<commit_before>function uniqueId() {
  return Date.now() + 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    var r = Math.random()*16|0, v = c === 'x' ? r : (r&0x3|0x8);
    return v.toString(16);
  });
}

var listener = null;
var setupMessageListener = function(siteName, buildEnv) {
  var ref = window.ENV.firebase.root().child('management/sites/' + siteName + '/messages');
  if(listener) {
    ref.off('child_added', listener);
    listener = null;
  }

  var initialIds = {};
  ref.once('value', function(totalData) {
    var val = totalData.val();

    for(var key in val) {
      initialIds[key] = true;
    }

    listener = ref.on('child_added', function(snap) {
      var now = Date.now();
      var message = snap.val();
      var id = snap.name();

      if(!initialIds[id]) {
        if(message.code === 'BUILD') {
          if(message.status === 0) {
            window.ENV.notify('success', 'Site build complete', { icon: 'refresh' });
          } else {
            window.ENV.notify('danger', 'Site build failed', { icon: 'remove' });
          }
          buildEnv.set('building', false);
        }
      }
    });
  });
};

export default Ember.Route.extend({
  notifications: [],

  getBuildEnvironment: function () {

    var route = this;

    if (Ember.isEmpty(route.get('buildEnvironment.isInitialized'))) {
      Ember.Logger.log('ApplicationRoute::getBuildEnvironment');
    } else {
      Ember.Logger.log('ApplicationRoute::getBuildEnvironment::skip');
      return Ember.RSVP.Promise.resolve();
    }

    var buildEnv = route.get('buildEnvironment');

    buildEnv.set('isInitialized', true);
    buildEnv.set('keepReload', Ember.$('meta[name="keepReload"]').attr('content'));

    var siteName = Ember.$('meta[name="siteName"]').attr('content');

    buildEnv.set('siteDisplayName', window.ENV.selfHosted ? siteName.replace('/,1/g', '.') : siteName);
    buildEnv.set('selfHosted', window.ENV.selfHosted);

    if (!window.ENV.selfHosted) {
      window.ENV.siteDNS = siteName + '.webhook.org';
    }
    window.ENV.firebaseRoot.child('/management/sites/' + siteName + '/dns').on('value', function (snap) {
      if (snap.val()) {
        window.ENV.siteDNS = snap.val();
      }
    });

    if (window.ENV.uploadUrl.indexOf('http://') !== 0) {
      window.ENV.uploadUrl = 'http://' + window.ENV.uploadUrl;
    }
    if (window.ENV.uploadUrl.substr(-1) !== '/') {
      window.ENV.uploadUrl = window.ENV.uploadUrl + '/';
    }

    var req = new XMLHttpRequest();
    req.open('GET', document.location, false);
    req.send(null);
    var headers = req.getAllResponseHeaders().toLowerCase();

    return new Ember.RSVP.Promise(function (resolve, reject) {

      if (headers.indexOf('x-webhook-local') !== -1) {
        buildEnv.set('local', true);
        var localSocket = Ember.Object.create({
          socket: new window.WebSocket('ws://' + document.location.hostname + ':6557')
        });

        buildEnv.set('localSocket', localSocket);

        localSocket.reopen({
          connected: function () {
            return this.get('socket.readyState') === 1;
          }.property('socket.readyState')
        });

        localSocket.socket.onmessage = function (event) {
          var storedCallback;
          if (event.data === 'done') {
            storedCallback = localSocket.get('doneCallback');
            localSocket.set('doneCallback', null);

            if (storedCallback) {
              storedCallback();
            }
          } else if (event.data.indexOf('done:') === 0) {
            var data = JSON.parse(event.data.replace('done:', ''));

            storedCallback = localSocket.get('doneCallback');
            localSocket.set('doneCallback', null);

            if (storedCallback) {
              storedCallback(data);
            }
          } else if (event.data.indexOf('message:') === 0) {
            var message = JSON.parse(event.data.replace('message:', ''));
            localSocket.set('message', message);
          }
        };

        localSocket.socket.onopen = function () {
          localSocket.set('connected', true);
          resolve();
        };

        localSocket.socket.onerror = function () {
          resolve();
        };

        if (!$('meta[name=suppressAlert]').attr('content')) {
          localSocket.socket.onclose = function () {
            localSocket.set('lostConnection', true);
          };
        }

        // Shut down LiveReload
        if (window.LiveReload && !buildEnv.get('keepReload')) {
          var shutDown = new CustomEvent('LiveReloadShutDown');
          document.addEventListener("LiveReloadConnect", function () {
            document.dispatchEvent(shutDown);
          }, false);
        }

      } else {
        resolve();
      }
    });
  },

  getMessageSuport: function () {

    var route = this;

    if (Ember.isEmpty(route.get('session.supportedMessages'))) {
      Ember.Logger.log('ApplicationRoute::getMessageSupport');
    } else {
      Ember.Logger.log('ApplicationRoute::getMessageSupport::skip');
      return Ember.RSVP.Promise.resolve();
    }

    route.set('session.supportedMessages', Ember.Object.create());

    return new Ember.RSVP.Promise(function (resolve, reject) {

      var localSocket = route.get('buildEnvironment.localSocket');
      if (!localSocket || !localSocket.connected) {

        Ember.$.ajax({
          dataType: 'jsonp',
          jsonpCallback: 'supportedJSONPCallback',
          url: '/.wh/_supported/',
          success: resolve,
          error: reject
        });

      } else {

        route.gruntCommand('supported_messages', resolve);

      }

    }).then(function (messages) {
      messages.forEach(function (message) {
        route.get('session.supportedMessages').set(message, true);
      });
      Ember.Logger.info('Server Messages Supported:', messages.join(', '));
    }, function (error) {
      Ember.Logger.warn('Failed to retrieve supported grunt messages.');
    });
  },

  // Confirm that user has completed validation steps
  validateUser: function (user) {

    Ember.Logger.log('ApplicationRoute::validateUser');

    var session = this.get('session');
    var siteName = session.get('site.name');

    var managementSiteRef = window.ENV.firebaseRoot.child('management/sites/' + siteName);

    return new Ember.RSVP.Promise(function (resolve, reject) {

      var getToken = function (snapshot) {
        var token = snapshot.val();
        session.set('site.token', token);

        window.ENV.firebase = window.ENV.firebaseRoot.child('buckets/' + siteName + '/' + token + '/dev');

        // if you just logged in, we have to set the firebase property
        DS.FirebaseAdapter.reopen({
          firebase: window.ENV.firebase
        });

        resolve(user);
      };

      managementSiteRef.child('key').once('value', getToken, function (error) {

        if (error.code === 'PERMISSION_DENIED') {
          var escapedEmail = user.email.replace(/\./g, ',1');
          // Try to add to user list, if this is allowed they were a potential user
          managementSiteRef.child('users').child(escapedEmail).set(user.email, function () {
            managementSiteRef.root().child('management/users').child(escapedEmail).child('sites/user').child(siteName).set(true, function (error) {
              // Try to delete self from potential user list
              managementSiteRef.child('potential_users').child(escapedEmail).remove(function () {
                // Redo original authorization call
                managementSiteRef.child('key').once('value', getToken, reject);
              }, reject);
            });
          }, reject);
        } else {
          reject(error);
        }

      });

    });

  },

  initializeUser: function (user) {

    Ember.Logger.log('ApplicationRoute::initializeUser');

    var route = this;
    var session = route.get('session');
    var siteName = session.get('site.name');

    var managementSiteRef = window.ENV.firebaseRoot.child('management/sites/' + siteName);

    if (route.get('buildEnvironment.local') === false) {
      setupMessageListener(siteName, route.get('buildEnvironment'));
    }

    Ember.Logger.info('Logged in as ' + user.email);

    var escapedEmail = user.email.replace(/\./g, ',1');

    var ownerCheck = new Ember.RSVP.Promise(function (resolve, reject) {
      session.set('isOwner', false);
      managementSiteRef.once('value', function (snapshot) {
        var siteData = snapshot.val();

        if (siteData.owners[escapedEmail]) {
          Ember.Logger.info('Logged in user is a site owner.');
          session.set('isOwner', true);
        } else if (siteData.users[escapedEmail]) {
          Ember.Logger.info('Logged in user is a site user.');
        } else {
          Ember.Logger.error('Logged in user is neither a site owner or site user??');
        }

        Ember.run(null, resolve);

      });
    });

    // Default billing values
    var billing = Ember.Object.create({
      active: true,
      status: 'paid',
      url: 'http://billing.webhook.com/site/' + siteName + '/',
    });
    billing.reopen({
      isPaid: function () {
        return this.get('status') === 'paid';
      }.property('status'),
      isTrial: function () {
        return this.get('status') === 'trialing';
      }.property('status')
    });
    session.set('billing', billing);

    // Grab actual billing values
    var billingRef = window.ENV.firebaseRoot.child('billing/sites/' + siteName);

    var activeCheck = new Ember.RSVP.Promise(function (resolve, reject) {
      billingRef.child('active').once('value', function (snapshot) {
        session.set('billing.active', snapshot.val());
        Ember.run(null, resolve);
      });
    });

    var statusCheck = new Ember.RSVP.Promise(function (resolve, reject) {
      billingRef.child('status').once('value', function (snapshot) {
        session.set('billing.status', snapshot.val());
        Ember.run(null, resolve);
      });
    });

    var endTrialCheck = new Ember.RSVP.Promise(function (resolve, reject) {
      billingRef.child('endTrial').once('value', function (snapshot) {
        var endTrial = snapshot.val();
        if (endTrial) {
          var endTrialDays = Math.ceil(moment(snapshot.val()).diff(moment(), 'days', true));
          session.set('billing.endTrial', endTrial);
          session.set('billing.endTrialDays', endTrialDays);
          session.set('billing.endTrialIsLastDay', endTrialDays === 1);
        }
        Ember.run(null, resolve);
      });
    });

    return Ember.RSVP.Promise.all([ownerCheck, activeCheck, statusCheck, endTrialCheck]).then(function () {
      session.set('user', user);
    });

  },

  getSession: function () {

    var route = this;
    var session = route.get('session');

    if (Ember.isEmpty(session.get('auth'))) {
      Ember.Logger.log('ApplicationRoute::getSession');
    } else {
      Ember.Logger.log('ApplicationRoute::getSession::skip');
      return Ember.RSVP.Promise.resolve();
    }

    var siteName = session.get('site.name');

    var managementSiteRef = window.ENV.firebaseRoot.child('management/sites/' + siteName);

    return new Ember.RSVP.Promise(function (resolve, reject) {

      var firebaseAuth = new FirebaseSimpleLogin(window.ENV.firebaseRoot, function (error, user) {

        if (route.get('isDestroyed')) {
          return;
        }

        if (user) {
          // Logged in
          route.validateUser(user).then(route.initializeUser.bind(route)).then(resolve);
        } else if (error) {
          // an error occurred while attempting login
          reject(error);
        } else {
          // user is logged out
          session.set('user', null);
          session.set('site.token', null);
          resolve();
        }

      });

      session.set('auth', firebaseAuth);

    });
  },

  beforeModel: function () {
    Ember.Logger.log('ApplicationRoute::beforeModel');

    var route = this;

    return this.getBuildEnvironment().then(function () {
      return Ember.RSVP.Promise.all([
        route.getSession(),
        route.getMessageSuport()
      ]);
    });
  },

  setupController: function (controller) {
    controller.set('notifications', this.get('notifications'));
    this._super.apply(this, arguments);
  },

  gruntCommand: function (command, callback) {
    Ember.Logger.log('%cgruntCommand -> ' + command, 'color: purple; font-weight: bold');

    var localSocket = this.get('buildEnvironment.localSocket');
    if (localSocket && localSocket.connected) {
      localSocket.socket.send(command);
      if (callback) {
        localSocket.doneCallback = callback;
      }
    }
  },

  actions: {
    logoutUser: function () {

      window.ENV.firebase.child('presence/online').child(this.get('session.user.uid')).remove();

      this.get('session.auth').logout();
      this.set('session.user', null);
      this.transitionTo('login');
    },

    notify: function (type, message, options) {

      options = options || {};

      var notifications = this.get('notifications'),
          notification = Ember.Object.create({
            className: 'wy-tray-item' + (type ? '-' + type : ''),
            message: message
          });

      if (options.icon) {
        notification.set('iconClass', 'icon icon-' + options.icon);
      }

      if (options.className) {
        notification.set('extraClassName', options.className);
      }

      notifications.pushObject(notification);

      Ember.run.later(this, function () {
        notification.set('state', 'on');
      }, 10);

      Ember.run.later(this, function () {
        notification.set('state', null);
      }, 4500);

      Ember.run.later(this, function () {
        notifications.removeObject(notification);
      }, 5000);

    },

    chooseLanguage: function (language) {
      Ember.Logger.log('Changing language to %@'.fmt(language));
      window.localStorage.setItem('webhook-cms-language', language);

      Ember.Logger.log('Resetting app.');
      window.App.reset();
    },

    buildSignal: function (publishDate) {
      Ember.Logger.info('Sending build signal:%@'.fmt(publishDate || 'No publish date.'));

      var route = this;

      var user = route.get('session.user.email');

      if (route.get('buildEnvironment.local') === false) {

        var data = {
          userid: user,
          sitename: route.get('session.site.name'),
          id: uniqueId()
        };

        if (publishDate) {
          data.build_time = publishDate;
        }

        window.ENV.firebase.root().child('management/commands/build/' + route.get('session.site.name')).set(data);
        route.set('buildEnvironment.building', true);

      } else {

        route.send('gruntCommand', 'build', function () {
          route.send('notify', 'success', 'Local build complete', { icon: 'refresh' });
        });

      }
    },

    gruntCommand: function (command, callback) {
      this.gruntCommand.apply(this, arguments);
    }
  }
});
<commit_msg>build signal is not explicitly set to false<commit_after>function uniqueId() {
  return Date.now() + 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    var r = Math.random()*16|0, v = c === 'x' ? r : (r&0x3|0x8);
    return v.toString(16);
  });
}

var listener = null;
var setupMessageListener = function(siteName, buildEnv) {
  var ref = window.ENV.firebase.root().child('management/sites/' + siteName + '/messages');
  if(listener) {
    ref.off('child_added', listener);
    listener = null;
  }

  var initialIds = {};
  ref.once('value', function(totalData) {
    var val = totalData.val();

    for(var key in val) {
      initialIds[key] = true;
    }

    listener = ref.on('child_added', function(snap) {
      var now = Date.now();
      var message = snap.val();
      var id = snap.name();

      if(!initialIds[id]) {
        if(message.code === 'BUILD') {
          if(message.status === 0) {
            window.ENV.notify('success', 'Site build complete', { icon: 'refresh' });
          } else {
            window.ENV.notify('danger', 'Site build failed', { icon: 'remove' });
          }
          buildEnv.set('building', false);
        }
      }
    });
  });
};

export default Ember.Route.extend({
  notifications: [],

  getBuildEnvironment: function () {

    var route = this;

    if (Ember.isEmpty(route.get('buildEnvironment.isInitialized'))) {
      Ember.Logger.log('ApplicationRoute::getBuildEnvironment');
    } else {
      Ember.Logger.log('ApplicationRoute::getBuildEnvironment::skip');
      return Ember.RSVP.Promise.resolve();
    }

    var buildEnv = route.get('buildEnvironment');

    buildEnv.set('isInitialized', true);
    buildEnv.set('keepReload', Ember.$('meta[name="keepReload"]').attr('content'));

    var siteName = Ember.$('meta[name="siteName"]').attr('content');

    buildEnv.set('siteDisplayName', window.ENV.selfHosted ? siteName.replace('/,1/g', '.') : siteName);
    buildEnv.set('selfHosted', window.ENV.selfHosted);

    if (!window.ENV.selfHosted) {
      window.ENV.siteDNS = siteName + '.webhook.org';
    }
    window.ENV.firebaseRoot.child('/management/sites/' + siteName + '/dns').on('value', function (snap) {
      if (snap.val()) {
        window.ENV.siteDNS = snap.val();
      }
    });

    if (window.ENV.uploadUrl.indexOf('http://') !== 0) {
      window.ENV.uploadUrl = 'http://' + window.ENV.uploadUrl;
    }
    if (window.ENV.uploadUrl.substr(-1) !== '/') {
      window.ENV.uploadUrl = window.ENV.uploadUrl + '/';
    }

    var req = new XMLHttpRequest();
    req.open('GET', document.location, false);
    req.send(null);
    var headers = req.getAllResponseHeaders().toLowerCase();

    return new Ember.RSVP.Promise(function (resolve, reject) {

      if (headers.indexOf('x-webhook-local') !== -1) {
        buildEnv.set('local', true);
        var localSocket = Ember.Object.create({
          socket: new window.WebSocket('ws://' + document.location.hostname + ':6557')
        });

        buildEnv.set('localSocket', localSocket);

        localSocket.reopen({
          connected: function () {
            return this.get('socket.readyState') === 1;
          }.property('socket.readyState')
        });

        localSocket.socket.onmessage = function (event) {
          var storedCallback;
          if (event.data === 'done') {
            storedCallback = localSocket.get('doneCallback');
            localSocket.set('doneCallback', null);

            if (storedCallback) {
              storedCallback();
            }
          } else if (event.data.indexOf('done:') === 0) {
            var data = JSON.parse(event.data.replace('done:', ''));

            storedCallback = localSocket.get('doneCallback');
            localSocket.set('doneCallback', null);

            if (storedCallback) {
              storedCallback(data);
            }
          } else if (event.data.indexOf('message:') === 0) {
            var message = JSON.parse(event.data.replace('message:', ''));
            localSocket.set('message', message);
          }
        };

        localSocket.socket.onopen = function () {
          localSocket.set('connected', true);
          resolve();
        };

        localSocket.socket.onerror = function () {
          resolve();
        };

        if (!$('meta[name=suppressAlert]').attr('content')) {
          localSocket.socket.onclose = function () {
            localSocket.set('lostConnection', true);
          };
        }

        // Shut down LiveReload
        if (window.LiveReload && !buildEnv.get('keepReload')) {
          var shutDown = new CustomEvent('LiveReloadShutDown');
          document.addEventListener("LiveReloadConnect", function () {
            document.dispatchEvent(shutDown);
          }, false);
        }

      } else {
        resolve();
      }
    });
  },

  getMessageSuport: function () {

    var route = this;

    if (Ember.isEmpty(route.get('session.supportedMessages'))) {
      Ember.Logger.log('ApplicationRoute::getMessageSupport');
    } else {
      Ember.Logger.log('ApplicationRoute::getMessageSupport::skip');
      return Ember.RSVP.Promise.resolve();
    }

    route.set('session.supportedMessages', Ember.Object.create());

    return new Ember.RSVP.Promise(function (resolve, reject) {

      var localSocket = route.get('buildEnvironment.localSocket');
      if (!localSocket || !localSocket.connected) {

        Ember.$.ajax({
          dataType: 'jsonp',
          jsonpCallback: 'supportedJSONPCallback',
          url: '/.wh/_supported/',
          success: resolve,
          error: reject
        });

      } else {

        route.gruntCommand('supported_messages', resolve);

      }

    }).then(function (messages) {
      messages.forEach(function (message) {
        route.get('session.supportedMessages').set(message, true);
      });
      Ember.Logger.info('Server Messages Supported:', messages.join(', '));
    }, function (error) {
      Ember.Logger.warn('Failed to retrieve supported grunt messages.');
    });
  },

  // Confirm that user has completed validation steps
  validateUser: function (user) {

    Ember.Logger.log('ApplicationRoute::validateUser');

    var session = this.get('session');
    var siteName = session.get('site.name');

    var managementSiteRef = window.ENV.firebaseRoot.child('management/sites/' + siteName);

    return new Ember.RSVP.Promise(function (resolve, reject) {

      var getToken = function (snapshot) {
        var token = snapshot.val();
        session.set('site.token', token);

        window.ENV.firebase = window.ENV.firebaseRoot.child('buckets/' + siteName + '/' + token + '/dev');

        // if you just logged in, we have to set the firebase property
        DS.FirebaseAdapter.reopen({
          firebase: window.ENV.firebase
        });

        resolve(user);
      };

      managementSiteRef.child('key').once('value', getToken, function (error) {

        if (error.code === 'PERMISSION_DENIED') {
          var escapedEmail = user.email.replace(/\./g, ',1');
          // Try to add to user list, if this is allowed they were a potential user
          managementSiteRef.child('users').child(escapedEmail).set(user.email, function () {
            managementSiteRef.root().child('management/users').child(escapedEmail).child('sites/user').child(siteName).set(true, function (error) {
              // Try to delete self from potential user list
              managementSiteRef.child('potential_users').child(escapedEmail).remove(function () {
                // Redo original authorization call
                managementSiteRef.child('key').once('value', getToken, reject);
              }, reject);
            });
          }, reject);
        } else {
          reject(error);
        }

      });

    });

  },

  initializeUser: function (user) {

    Ember.Logger.log('ApplicationRoute::initializeUser');

    var route = this;
    var session = route.get('session');
    var siteName = session.get('site.name');

    var managementSiteRef = window.ENV.firebaseRoot.child('management/sites/' + siteName);

    if (route.get('buildEnvironment.local') === false) {
      setupMessageListener(siteName, route.get('buildEnvironment'));
    }

    Ember.Logger.info('Logged in as ' + user.email);

    var escapedEmail = user.email.replace(/\./g, ',1');

    var ownerCheck = new Ember.RSVP.Promise(function (resolve, reject) {
      session.set('isOwner', false);
      managementSiteRef.once('value', function (snapshot) {
        var siteData = snapshot.val();

        if (siteData.owners[escapedEmail]) {
          Ember.Logger.info('Logged in user is a site owner.');
          session.set('isOwner', true);
        } else if (siteData.users[escapedEmail]) {
          Ember.Logger.info('Logged in user is a site user.');
        } else {
          Ember.Logger.error('Logged in user is neither a site owner or site user??');
        }

        Ember.run(null, resolve);

      });
    });

    // Default billing values
    var billing = Ember.Object.create({
      active: true,
      status: 'paid',
      url: 'http://billing.webhook.com/site/' + siteName + '/',
    });
    billing.reopen({
      isPaid: function () {
        return this.get('status') === 'paid';
      }.property('status'),
      isTrial: function () {
        return this.get('status') === 'trialing';
      }.property('status')
    });
    session.set('billing', billing);

    // Grab actual billing values
    var billingRef = window.ENV.firebaseRoot.child('billing/sites/' + siteName);

    var activeCheck = new Ember.RSVP.Promise(function (resolve, reject) {
      billingRef.child('active').once('value', function (snapshot) {
        session.set('billing.active', snapshot.val());
        Ember.run(null, resolve);
      });
    });

    var statusCheck = new Ember.RSVP.Promise(function (resolve, reject) {
      billingRef.child('status').once('value', function (snapshot) {
        session.set('billing.status', snapshot.val());
        Ember.run(null, resolve);
      });
    });

    var endTrialCheck = new Ember.RSVP.Promise(function (resolve, reject) {
      billingRef.child('endTrial').once('value', function (snapshot) {
        var endTrial = snapshot.val();
        if (endTrial) {
          var endTrialDays = Math.ceil(moment(snapshot.val()).diff(moment(), 'days', true));
          session.set('billing.endTrial', endTrial);
          session.set('billing.endTrialDays', endTrialDays);
          session.set('billing.endTrialIsLastDay', endTrialDays === 1);
        }
        Ember.run(null, resolve);
      });
    });

    return Ember.RSVP.Promise.all([ownerCheck, activeCheck, statusCheck, endTrialCheck]).then(function () {
      session.set('user', user);
    });

  },

  getSession: function () {

    var route = this;
    var session = route.get('session');

    if (Ember.isEmpty(session.get('auth'))) {
      Ember.Logger.log('ApplicationRoute::getSession');
    } else {
      Ember.Logger.log('ApplicationRoute::getSession::skip');
      return Ember.RSVP.Promise.resolve();
    }

    var siteName = session.get('site.name');

    var managementSiteRef = window.ENV.firebaseRoot.child('management/sites/' + siteName);

    return new Ember.RSVP.Promise(function (resolve, reject) {

      var firebaseAuth = new FirebaseSimpleLogin(window.ENV.firebaseRoot, function (error, user) {

        if (route.get('isDestroyed')) {
          return;
        }

        if (user) {
          // Logged in
          route.validateUser(user).then(route.initializeUser.bind(route)).then(resolve);
        } else if (error) {
          // an error occurred while attempting login
          reject(error);
        } else {
          // user is logged out
          session.set('user', null);
          session.set('site.token', null);
          resolve();
        }

      });

      session.set('auth', firebaseAuth);

    });
  },

  beforeModel: function () {
    Ember.Logger.log('ApplicationRoute::beforeModel');

    var route = this;

    return this.getBuildEnvironment().then(function () {
      return Ember.RSVP.Promise.all([
        route.getSession(),
        route.getMessageSuport()
      ]);
    });
  },

  setupController: function (controller) {
    controller.set('notifications', this.get('notifications'));
    this._super.apply(this, arguments);
  },

  gruntCommand: function (command, callback) {
    Ember.Logger.log('%cgruntCommand -> ' + command, 'color: purple; font-weight: bold');

    var localSocket = this.get('buildEnvironment.localSocket');
    if (localSocket && localSocket.connected) {
      localSocket.socket.send(command);
      if (callback) {
        localSocket.doneCallback = callback;
      }
    }
  },

  actions: {
    logoutUser: function () {

      window.ENV.firebase.child('presence/online').child(this.get('session.user.uid')).remove();

      this.get('session.auth').logout();
      this.set('session.user', null);
      this.transitionTo('login');
    },

    notify: function (type, message, options) {

      options = options || {};

      var notifications = this.get('notifications'),
          notification = Ember.Object.create({
            className: 'wy-tray-item' + (type ? '-' + type : ''),
            message: message
          });

      if (options.icon) {
        notification.set('iconClass', 'icon icon-' + options.icon);
      }

      if (options.className) {
        notification.set('extraClassName', options.className);
      }

      notifications.pushObject(notification);

      Ember.run.later(this, function () {
        notification.set('state', 'on');
      }, 10);

      Ember.run.later(this, function () {
        notification.set('state', null);
      }, 4500);

      Ember.run.later(this, function () {
        notifications.removeObject(notification);
      }, 5000);

    },

    chooseLanguage: function (language) {
      Ember.Logger.log('Changing language to %@'.fmt(language));
      window.localStorage.setItem('webhook-cms-language', language);

      Ember.Logger.log('Resetting app.');
      window.App.reset();
    },

    buildSignal: function (publishDate) {
      Ember.Logger.info('Sending build signal:%@'.fmt(publishDate || 'No publish date.'));

      var route = this;

      var user = route.get('session.user.email');

      if (!route.get('buildEnvironment.local')) {

        var data = {
          userid: user,
          sitename: route.get('session.site.name'),
          id: uniqueId()
        };

        if (publishDate) {
          data.build_time = publishDate;
        }

        window.ENV.firebase.root().child('management/commands/build/' + route.get('session.site.name')).set(data);
        route.set('buildEnvironment.building', true);

      } else {

        route.send('gruntCommand', 'build', function () {
          route.send('notify', 'success', 'Local build complete', { icon: 'refresh' });
        });

      }
    },

    gruntCommand: function (command, callback) {
      this.gruntCommand.apply(this, arguments);
    }
  }
});
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import { StyleSheet, Text, View, Animated, TouchableOpacity, Platform } from 'react-native';
import ActionButtonItem from './ActionButtonItem';
import { shadowStyle, alignItemsMap, getTouchableComponent, isAndroid, touchableBackground, DEFAULT_ACTIVE_OPACITY } from './shared';

export default class ActionButton extends Component {
  constructor(props) {
    super(props);

    this.state = {
      resetToken: props.resetToken,
      active: props.active,
    }

    this.anim = new Animated.Value(props.active ? 1 : 0);
    this.timeout = null;
  }

  componentWillUnmount() {
    clearTimeout(this.timeout);
  }

  componentWillReceiveProps(nextProps)
  {
     if (nextProps.resetToken !== this.state.resetToken)
     {
        if (nextProps.active === false && this.state.active === true)
        {
            if (this.props.onReset) this.props.onReset();
            Animated.spring(this.anim, { toValue: 0 }).start();
            setTimeout(() => this.setState({ active: false, resetToken: nextProps.resetToken }), 250);
            return;
        }

        if (nextProps.active === true && this.state.active === false)
        {
            Animated.spring(this.anim, { toValue: 1 }).start();
            this.setState({ active: true, resetToken: nextProps.resetToken });
            return;
        }

        this.setState({ resetToken: nextProps.resetToken, active: nextProps.active });
     }
  }

  //////////////////////
  // STYLESHEET GETTERS
  //////////////////////

  getOrientation() {
    return { alignItems: alignItemsMap[this.props.position] };
  }

  getOffsetXY() {
    return {
      // paddingHorizontal: this.props.offsetX,
      paddingVertical: this.props.offsetY
    };
  }

  getOverlayStyles() {
    return [
      styles.overlay,
      {
        elevation: this.props.elevation,
        justifyContent: this.props.verticalOrientation === 'up' ? 'flex-end' : 'flex-start'
      }
    ]
  }

  //////////////////////
  // RENDER METHODS
  //////////////////////

  render() {
    return (
      <View pointerEvents="box-none" style={this.getOverlayStyles()}>
        <Animated.View pointerEvents="none" style={[this.getOverlayStyles(), {
          backgroundColor: this.props.bgColor,
          opacity: this.anim.interpolate({
            inputRange: [0, 1],
            outputRange: [0, this.props.bgOpacity]
          }),
        }]}>
          {this.props.backdrop}
        </Animated.View>
        <View pointerEvents="box-none" style={[this.getOverlayStyles(), this.getOrientation(), this.getOffsetXY()]}>
          {(this.state.active && !this.props.backgroundTappable) && this._renderTappableBackground()}

          {this.props.verticalOrientation === 'up' &&
            this.props.children && this._renderActions()}
          {this._renderMainButton()}
          {this.props.verticalOrientation === 'down' &&
            this.props.children && this._renderActions()}
        </View>
      </View>
    );
  }

  _renderMainButton() {
    const animatedViewStyle = {
      transform: [{
        scale: this.anim.interpolate({
          inputRange: [0, 1],
          outputRange: [1, this.props.outRangeScale]
        }),
      }, {
        rotate: this.anim.interpolate({
          inputRange: [0, 1],
          outputRange: ['0deg', this.props.degrees + 'deg']
        })
      }],
    };

    const wrapperStyle = {
      backgroundColor: this.anim.interpolate({
        inputRange: [0, 1],
        outputRange: [this.props.buttonColor, (this.props.btnOutRange || this.props.buttonColor)]
      }),
      width: this.props.size,
      height: this.props.size,
      borderRadius: this.props.size / 2,
    };

    const buttonStyle = {
      width: this.props.size,
      height: this.props.size,
      borderRadius: this.props.size / 2,
      alignItems: 'center',
      justifyContent: 'center',
    };

    const Touchable = getTouchableComponent(this.props.useNativeFeedback);

    return (
      <View style={{ paddingHorizontal: this.props.offsetX }}>
        <Touchable
          background={touchableBackground}
          activeOpacity={this.props.activeOpacity}
          onLongPress={this.props.onLongPress}
          onPress={() => {
            this.props.onPress()
            if (this.props.children) this.animateButton()
          }}>
          <Animated.View style={[wrapperStyle, !this.props.hideShadow && shadowStyle]}>
            <Animated.View style={[buttonStyle, animatedViewStyle]}>
              {this._renderButtonIcon()}
            </Animated.View>
          </Animated.View>
        </Touchable>
      </View>
    );
  }

  _renderButtonIcon() {
    const { icon, btnOutRangeTxt, buttonTextStyle, buttonText } = this.props;
    if (icon) return icon;

    const textColor = buttonTextStyle.color || 'rgba(255,255,255,1)'

    return (
      <Animated.Text style={[styles.btnText, buttonTextStyle, {
        color: this.anim.interpolate({
          inputRange: [0, 1],
          outputRange: [textColor, (btnOutRangeTxt || textColor)]
        })
      }]}>
        {buttonText}
      </Animated.Text>
    )
  }

  _renderActions() {
    const { children, verticalOrientation } = this.props;

    if (!this.state.active) return null;

    const actionButtons = !Array.isArray(children) ? [children] : children;

    const actionStyle = {
      flex: 1,
      alignSelf: 'stretch',
      // backgroundColor: 'purple',
      justifyContent: verticalOrientation === 'up' ? 'flex-end' : 'flex-start',
      paddingTop: this.props.verticalOrientation === 'down' ? this.props.spacing : 0
    };

    return (
      <View style={actionStyle} pointerEvents={'box-none'}>
        {actionButtons.map((ActionButton, idx) => (
          <ActionButtonItem
            key={idx}
            anim={this.anim}
            {...this.props}
            {...ActionButton.props}
            parentSize={this.props.size}
            btnColor={this.props.btnOutRange}
            onPress={() => {
              if (this.props.autoInactive){
                this.timeout = setTimeout(this.reset.bind(this), 200);
              }
              ActionButton.props.onPress();
            }}
          />
        ))}
      </View>
    );
  }

  _renderTappableBackground() {
    return (
      <TouchableOpacity
        activeOpacity={1}
        style={this.getOverlayStyles()}
        onPress={this.reset.bind(this)}
      />
    );
  }

  //////////////////////
  // Animation Methods
  //////////////////////

  animateButton(animate=true) {
    if (this.state.active) return this.reset();

    if (animate) {
      Animated.spring(this.anim, { toValue: 1 }).start();
    } else {
      this.anim.setValue(1);
    }

    this.setState({ active: true, resetToken: this.state.resetToken });
  }

  reset(animate=true) {
    if (this.props.onReset) this.props.onReset();

    if (animate) {
      Animated.spring(this.anim, { toValue: 0 }).start();
    } else {
      this.anim.setValue(0);
    }

    setTimeout(() => this.setState({ active: false, resetToken: this.state.resetToken }), 250);
  }
}

ActionButton.Item = ActionButtonItem;

ActionButton.propTypes = {
  resetToken: PropTypes.any,
  active: PropTypes.bool,

  position: PropTypes.string,
  elevation: PropTypes.number,

  hideShadow: PropTypes.bool,

  bgColor: PropTypes.string,
  bgOpacity: PropTypes.number,
  buttonColor: PropTypes.string,
  buttonTextStyle: Text.propTypes.style,
  buttonText: PropTypes.string,

  offsetX: PropTypes.number,
  offsetY: PropTypes.number,
  spacing: PropTypes.number,
  size: PropTypes.number,
  autoInactive: PropTypes.bool,
  onPress: PropTypes.func,
  backdrop: PropTypes.oneOfType([
    PropTypes.bool,
    PropTypes.object
  ]),
  degrees: PropTypes.number,
  verticalOrientation: PropTypes.oneOf(['up', 'down']),
  backgroundTappable: PropTypes.bool,
  useNativeFeedback: PropTypes.bool,
  activeOpacity: PropTypes.number,
};

ActionButton.defaultProps = {
  resetToken: null,
  active: false,
  bgColor: 'transparent',
  bgOpacity: 1,
  buttonColor: 'rgba(0,0,0,1)',
  buttonTextStyle: {},
  buttonText: '+',
  spacing: 20,
  outRangeScale: 1,
  autoInactive: true,
  onPress: () => {},
  backdrop: false,
  degrees: 45,
  position: 'right',
  offsetX: 30,
  offsetY: 30,
  size: 56,
  verticalOrientation: 'up',
  backgroundTappable: false,
  useNativeFeedback: true,
  activeOpacity: DEFAULT_ACTIVE_OPACITY,
};

const styles = StyleSheet.create({
  overlay: {
    position: 'absolute',
    bottom: 0,
    left: 0,
    right: 0,
    top: 0,
    backgroundColor: 'transparent',
  },
  btnText: {
    marginTop: -4,
    fontSize: 24,
    backgroundColor: 'transparent',
  },
});
<commit_msg>Add a prop to control the base zIndex of the action button.<commit_after>import React, { Component, PropTypes } from 'react';
import { StyleSheet, Text, View, Animated, TouchableOpacity, Platform } from 'react-native';
import ActionButtonItem from './ActionButtonItem';
import { shadowStyle, alignItemsMap, getTouchableComponent, isAndroid, touchableBackground, DEFAULT_ACTIVE_OPACITY } from './shared';

export default class ActionButton extends Component {
  constructor(props) {
    super(props);

    this.state = {
      resetToken: props.resetToken,
      active: props.active,
    }

    this.anim = new Animated.Value(props.active ? 1 : 0);
    this.timeout = null;
  }

  componentWillUnmount() {
    clearTimeout(this.timeout);
  }

  componentWillReceiveProps(nextProps)
  {
     if (nextProps.resetToken !== this.state.resetToken)
     {
        if (nextProps.active === false && this.state.active === true)
        {
            if (this.props.onReset) this.props.onReset();
            Animated.spring(this.anim, { toValue: 0 }).start();
            setTimeout(() => this.setState({ active: false, resetToken: nextProps.resetToken }), 250);
            return;
        }

        if (nextProps.active === true && this.state.active === false)
        {
            Animated.spring(this.anim, { toValue: 1 }).start();
            this.setState({ active: true, resetToken: nextProps.resetToken });
            return;
        }

        this.setState({ resetToken: nextProps.resetToken, active: nextProps.active });
     }
  }

  //////////////////////
  // STYLESHEET GETTERS
  //////////////////////

  getOrientation() {
    return { alignItems: alignItemsMap[this.props.position] };
  }

  getOffsetXY() {
    return {
      // paddingHorizontal: this.props.offsetX,
      paddingVertical: this.props.offsetY
    };
  }

  getOverlayStyles() {
    return [
      styles.overlay,
      {
        elevation: this.props.elevation,
        zIndex: this.props.zIndex,
        justifyContent: this.props.verticalOrientation === 'up' ? 'flex-end' : 'flex-start'
      }
    ]
  }

  //////////////////////
  // RENDER METHODS
  //////////////////////

  render() {
    return (
      <View pointerEvents="box-none" style={this.getOverlayStyles()}>
        <Animated.View pointerEvents="none" style={[this.getOverlayStyles(), {
          backgroundColor: this.props.bgColor,
          opacity: this.anim.interpolate({
            inputRange: [0, 1],
            outputRange: [0, this.props.bgOpacity]
          }),
        }]}>
          {this.props.backdrop}
        </Animated.View>
        <View pointerEvents="box-none" style={[this.getOverlayStyles(), this.getOrientation(), this.getOffsetXY()]}>
          {(this.state.active && !this.props.backgroundTappable) && this._renderTappableBackground()}

          {this.props.verticalOrientation === 'up' &&
            this.props.children && this._renderActions()}
          {this._renderMainButton()}
          {this.props.verticalOrientation === 'down' &&
            this.props.children && this._renderActions()}
        </View>
      </View>
    );
  }

  _renderMainButton() {
    const animatedViewStyle = {
      transform: [{
        scale: this.anim.interpolate({
          inputRange: [0, 1],
          outputRange: [1, this.props.outRangeScale]
        }),
      }, {
        rotate: this.anim.interpolate({
          inputRange: [0, 1],
          outputRange: ['0deg', this.props.degrees + 'deg']
        })
      }],
    };

    const wrapperStyle = {
      backgroundColor: this.anim.interpolate({
        inputRange: [0, 1],
        outputRange: [this.props.buttonColor, (this.props.btnOutRange || this.props.buttonColor)]
      }),
      width: this.props.size,
      height: this.props.size,
      borderRadius: this.props.size / 2,
    };

    const buttonStyle = {
      width: this.props.size,
      height: this.props.size,
      borderRadius: this.props.size / 2,
      alignItems: 'center',
      justifyContent: 'center',
    };

    const Touchable = getTouchableComponent(this.props.useNativeFeedback);

    return (
      <View style={{ paddingHorizontal: this.props.offsetX }}>
        <Touchable
          background={touchableBackground}
          activeOpacity={this.props.activeOpacity}
          onLongPress={this.props.onLongPress}
          onPress={() => {
            this.props.onPress()
            if (this.props.children) this.animateButton()
          }}>
          <Animated.View style={[wrapperStyle, !this.props.hideShadow && shadowStyle]}>
            <Animated.View style={[buttonStyle, animatedViewStyle]}>
              {this._renderButtonIcon()}
            </Animated.View>
          </Animated.View>
        </Touchable>
      </View>
    );
  }

  _renderButtonIcon() {
    const { icon, btnOutRangeTxt, buttonTextStyle, buttonText } = this.props;
    if (icon) return icon;

    const textColor = buttonTextStyle.color || 'rgba(255,255,255,1)'

    return (
      <Animated.Text style={[styles.btnText, buttonTextStyle, {
        color: this.anim.interpolate({
          inputRange: [0, 1],
          outputRange: [textColor, (btnOutRangeTxt || textColor)]
        })
      }]}>
        {buttonText}
      </Animated.Text>
    )
  }

  _renderActions() {
    const { children, verticalOrientation } = this.props;

    if (!this.state.active) return null;

    const actionButtons = !Array.isArray(children) ? [children] : children;

    const actionStyle = {
      flex: 1,
      alignSelf: 'stretch',
      // backgroundColor: 'purple',
      justifyContent: verticalOrientation === 'up' ? 'flex-end' : 'flex-start',
      paddingTop: this.props.verticalOrientation === 'down' ? this.props.spacing : 0
    };

    return (
      <View style={actionStyle} pointerEvents={'box-none'}>
        {actionButtons.map((ActionButton, idx) => (
          <ActionButtonItem
            key={idx}
            anim={this.anim}
            {...this.props}
            {...ActionButton.props}
            parentSize={this.props.size}
            btnColor={this.props.btnOutRange}
            onPress={() => {
              if (this.props.autoInactive){
                this.timeout = setTimeout(this.reset.bind(this), 200);
              }
              ActionButton.props.onPress();
            }}
          />
        ))}
      </View>
    );
  }

  _renderTappableBackground() {
    return (
      <TouchableOpacity
        activeOpacity={1}
        style={this.getOverlayStyles()}
        onPress={this.reset.bind(this)}
      />
    );
  }

  //////////////////////
  // Animation Methods
  //////////////////////

  animateButton(animate=true) {
    if (this.state.active) return this.reset();

    if (animate) {
      Animated.spring(this.anim, { toValue: 1 }).start();
    } else {
      this.anim.setValue(1);
    }

    this.setState({ active: true, resetToken: this.state.resetToken });
  }

  reset(animate=true) {
    if (this.props.onReset) this.props.onReset();

    if (animate) {
      Animated.spring(this.anim, { toValue: 0 }).start();
    } else {
      this.anim.setValue(0);
    }

    setTimeout(() => this.setState({ active: false, resetToken: this.state.resetToken }), 250);
  }
}

ActionButton.Item = ActionButtonItem;

ActionButton.propTypes = {
  resetToken: PropTypes.any,
  active: PropTypes.bool,

  position: PropTypes.string,
  elevation: PropTypes.number,
  zIndex: PropTypes.number,

  hideShadow: PropTypes.bool,

  bgColor: PropTypes.string,
  bgOpacity: PropTypes.number,
  buttonColor: PropTypes.string,
  buttonTextStyle: Text.propTypes.style,
  buttonText: PropTypes.string,

  offsetX: PropTypes.number,
  offsetY: PropTypes.number,
  spacing: PropTypes.number,
  size: PropTypes.number,
  autoInactive: PropTypes.bool,
  onPress: PropTypes.func,
  backdrop: PropTypes.oneOfType([
    PropTypes.bool,
    PropTypes.object
  ]),
  degrees: PropTypes.number,
  verticalOrientation: PropTypes.oneOf(['up', 'down']),
  backgroundTappable: PropTypes.bool,
  useNativeFeedback: PropTypes.bool,
  activeOpacity: PropTypes.number,
};

ActionButton.defaultProps = {
  resetToken: null,
  active: false,
  bgColor: 'transparent',
  bgOpacity: 1,
  buttonColor: 'rgba(0,0,0,1)',
  buttonTextStyle: {},
  buttonText: '+',
  spacing: 20,
  outRangeScale: 1,
  autoInactive: true,
  onPress: () => {},
  backdrop: false,
  degrees: 45,
  position: 'right',
  offsetX: 30,
  offsetY: 30,
  size: 56,
  verticalOrientation: 'up',
  backgroundTappable: false,
  useNativeFeedback: true,
  activeOpacity: DEFAULT_ACTIVE_OPACITY,
};

const styles = StyleSheet.create({
  overlay: {
    position: 'absolute',
    bottom: 0,
    left: 0,
    right: 0,
    top: 0,
    backgroundColor: 'transparent',
  },
  btnText: {
    marginTop: -4,
    fontSize: 24,
    backgroundColor: 'transparent',
  },
});
<|endoftext|>


<commit_before>require('../../support/test_helper');

var assert = require('../../support/assert');
var testClient = require('./support/test_client');
var fs = require('fs');
var http = require('http');

var PortedServerOptions = require('./support/ported_server_options');
var BaseController = require('../../../lib/cartodb/controllers/base');

describe('blend layer filtering', function() {

    var IMG_TOLERANCE_PER_MIL = 20;

    var httpRendererResourcesServer;

    var req2paramsFn;
    before(function(done) {
        req2paramsFn = BaseController.prototype.req2params;
        BaseController.prototype.req2params = PortedServerOptions.req2params;

        // Start a server to test external resources
        httpRendererResourcesServer = http.createServer( function(request, response) {
            var filename = __dirname + '/../../fixtures/http/light_nolabels-1-0-0.png';
            fs.readFile(filename, {encoding: 'binary'}, function(err, file) {
                response.writeHead(200);
                response.write(file, "binary");
                response.end();
            });
        });
        httpRendererResourcesServer.listen(8033, done);
    });

    after(function(done) {
        BaseController.prototype.req2params = req2paramsFn;
        httpRendererResourcesServer.close(done);
    });

    var mapConfig = {
        version: '1.2.0',
        layers: [
            {
                type: 'plain',
                options: {
                    color: '#fabada'
                }
            },
            {
                type: 'http',
                options: {
                    urlTemplate: 'http://127.0.0.1:8033/{s}/{z}/{x}/{y}.png',
                    subdomains: ['abcd']
                }
            },
            {
                type: 'mapnik',
                options: {
                    sql: 'SELECT * FROM populated_places_simple_reduced',
                    cartocss: '#layer { marker-fill:red; } #layer { marker-width: 2; }',
                    cartocss_version: '2.3.0',
                    geom_column: 'the_geom'
                }
            },
            {
                type: 'torque',
                options: {
                    sql: "SELECT * FROM populated_places_simple_reduced",
                    cartocss: [
                        'Map {',
                        '    buffer-size:0;',
                        '    -torque-frame-count:1;',
                        '    -torque-animation-duration:30;',
                        '    -torque-time-attribute:"cartodb_id";',
                        '    -torque-aggregation-function:"count(cartodb_id)";',
                        '    -torque-resolution:1;',
                        '    -torque-data-aggregation:linear;',
                        '}',
                        '#populated_places_simple_reduced{',
                        '    comp-op: multiply;',
                        '    marker-fill-opacity: 1;',
                        '    marker-line-color: #FFF;',
                        '    marker-line-width: 0;',
                        '    marker-line-opacity: 1;',
                        '    marker-type: rectangle;',
                        '    marker-width: 3;',
                        '    marker-fill: #FFCC00;',
                        '}'
                    ].join(' '),
                    cartocss_version: '2.3.0'
                }
            },
            {
                type: 'http',
                options: {
                    urlTemplate: 'http://127.0.0.1:8033/{s}/{z}/{x}/{y}.png',
                    subdomains: ['abcd']
                }
            },
            {
                type: 'torque',
                options: {
                    sql: "SELECT * FROM populated_places_simple_reduced " +
                        "where the_geom && ST_MakeEnvelope(-90, 0, 90, 65)",
                    cartocss: [
                        'Map {',
                        '    buffer-size:0;',
                        '    -torque-frame-count:1;',
                        '    -torque-animation-duration:30;',
                        '    -torque-time-attribute:"cartodb_id";',
                        '    -torque-aggregation-function:"count(cartodb_id)";',
                        '    -torque-resolution:1;',
                        '    -torque-data-aggregation:linear;',
                        '}',
                        '#populated_places_simple_reduced{',
                        '    comp-op: multiply;',
                        '    marker-fill-opacity: 1;',
                        '    marker-line-color: #FFF;',
                        '    marker-line-width: 0;',
                        '    marker-line-opacity: 1;',
                        '    marker-type: rectangle;',
                        '    marker-width: 3;',
                        '    marker-fill: #FFCC00;',
                        '}'
                    ].join(' '),
                    cartocss_version: '2.3.0'
                }
            }
        ]
    };

    var filteredLayersSuite = [
        [2, 2],
        [0, 1],
        [0, 2],
        [1, 2],
        [2, 1], // ordering doesn't matter
        [0, 3],
        [1, 3],
        [1, 2, 5],
        [1, 2, 3, 4]
    ];

    function blendPngFixture(layers) {
        return './test/fixtures/blend/blend-filtering-layers-' + layers.join('.') + '-zxy-1.0.0.png';
    }

    filteredLayersSuite.forEach(function(filteredLayers) {
        var layerFilter = filteredLayers.join(',');
        var tileRequest = {
            z: 1,
            x: 0,
            y: 0,
            layer: layerFilter,
            format: 'png'
        };

        it('should filter on ' + layerFilter + '/1/0/0.png', function (done) {
            testClient.getTileLayer(mapConfig, tileRequest, function(err, res) {
                assert.imageEqualsFile(res.body, blendPngFixture(filteredLayers), IMG_TOLERANCE_PER_MIL, function(err) {
                    assert.ok(!err);
                    done();
                });
            });
        });
    });
});
<commit_msg>Remove redis keys after each test<commit_after>var testHelper = require('../../support/test_helper');

var assert = require('../../support/assert');
var testClient = require('./support/test_client');
var fs = require('fs');
var http = require('http');

var PortedServerOptions = require('./support/ported_server_options');
var BaseController = require('../../../lib/cartodb/controllers/base');

describe('blend layer filtering', function() {

    var IMG_TOLERANCE_PER_MIL = 20;

    var httpRendererResourcesServer;

    var req2paramsFn;
    before(function(done) {
        req2paramsFn = BaseController.prototype.req2params;
        BaseController.prototype.req2params = PortedServerOptions.req2params;

        // Start a server to test external resources
        httpRendererResourcesServer = http.createServer( function(request, response) {
            var filename = __dirname + '/../../fixtures/http/light_nolabels-1-0-0.png';
            fs.readFile(filename, {encoding: 'binary'}, function(err, file) {
                response.writeHead(200);
                response.write(file, "binary");
                response.end();
            });
        });
        httpRendererResourcesServer.listen(8033, done);
    });

    after(function(done) {
        BaseController.prototype.req2params = req2paramsFn;
        httpRendererResourcesServer.close(done);
    });

    afterEach(function(done) {
        testHelper.deleteRedisKeys({'user:localhost:mapviews:global': 5}, done);
    });

    var mapConfig = {
        version: '1.2.0',
        layers: [
            {
                type: 'plain',
                options: {
                    color: '#fabada'
                }
            },
            {
                type: 'http',
                options: {
                    urlTemplate: 'http://127.0.0.1:8033/{s}/{z}/{x}/{y}.png',
                    subdomains: ['abcd']
                }
            },
            {
                type: 'mapnik',
                options: {
                    sql: 'SELECT * FROM populated_places_simple_reduced',
                    cartocss: '#layer { marker-fill:red; } #layer { marker-width: 2; }',
                    cartocss_version: '2.3.0',
                    geom_column: 'the_geom'
                }
            },
            {
                type: 'torque',
                options: {
                    sql: "SELECT * FROM populated_places_simple_reduced",
                    cartocss: [
                        'Map {',
                        '    buffer-size:0;',
                        '    -torque-frame-count:1;',
                        '    -torque-animation-duration:30;',
                        '    -torque-time-attribute:"cartodb_id";',
                        '    -torque-aggregation-function:"count(cartodb_id)";',
                        '    -torque-resolution:1;',
                        '    -torque-data-aggregation:linear;',
                        '}',
                        '#populated_places_simple_reduced{',
                        '    comp-op: multiply;',
                        '    marker-fill-opacity: 1;',
                        '    marker-line-color: #FFF;',
                        '    marker-line-width: 0;',
                        '    marker-line-opacity: 1;',
                        '    marker-type: rectangle;',
                        '    marker-width: 3;',
                        '    marker-fill: #FFCC00;',
                        '}'
                    ].join(' '),
                    cartocss_version: '2.3.0'
                }
            },
            {
                type: 'http',
                options: {
                    urlTemplate: 'http://127.0.0.1:8033/{s}/{z}/{x}/{y}.png',
                    subdomains: ['abcd']
                }
            },
            {
                type: 'torque',
                options: {
                    sql: "SELECT * FROM populated_places_simple_reduced " +
                        "where the_geom && ST_MakeEnvelope(-90, 0, 90, 65)",
                    cartocss: [
                        'Map {',
                        '    buffer-size:0;',
                        '    -torque-frame-count:1;',
                        '    -torque-animation-duration:30;',
                        '    -torque-time-attribute:"cartodb_id";',
                        '    -torque-aggregation-function:"count(cartodb_id)";',
                        '    -torque-resolution:1;',
                        '    -torque-data-aggregation:linear;',
                        '}',
                        '#populated_places_simple_reduced{',
                        '    comp-op: multiply;',
                        '    marker-fill-opacity: 1;',
                        '    marker-line-color: #FFF;',
                        '    marker-line-width: 0;',
                        '    marker-line-opacity: 1;',
                        '    marker-type: rectangle;',
                        '    marker-width: 3;',
                        '    marker-fill: #FFCC00;',
                        '}'
                    ].join(' '),
                    cartocss_version: '2.3.0'
                }
            }
        ]
    };

    var filteredLayersSuite = [
        [2, 2],
        [0, 1],
        [0, 2],
        [1, 2],
        [2, 1], // ordering doesn't matter
        [0, 3],
        [1, 3],
        [1, 2, 5],
        [1, 2, 3, 4]
    ];

    function blendPngFixture(layers) {
        return './test/fixtures/blend/blend-filtering-layers-' + layers.join('.') + '-zxy-1.0.0.png';
    }

    filteredLayersSuite.forEach(function(filteredLayers) {
        var layerFilter = filteredLayers.join(',');
        var tileRequest = {
            z: 1,
            x: 0,
            y: 0,
            layer: layerFilter,
            format: 'png'
        };

        it('should filter on ' + layerFilter + '/1/0/0.png', function (done) {
            testClient.getTileLayer(mapConfig, tileRequest, function(err, res) {
                assert.imageEqualsFile(res.body, blendPngFixture(filteredLayers), IMG_TOLERANCE_PER_MIL, function(err) {
                    assert.ok(!err);
                    done();
                });
            });
        });
    });
});
<|endoftext|>


<commit_before>import { run } from '@ember/runloop';
import $ from 'jquery';
import { resolve } from 'rsvp';
import DS from 'ember-data';
import { module, test } from 'qunit';
import { setupStore } from '../helpers/store';
import HasManyQuery from 'ember-data-has-many-query';

var env;

var Post = DS.Model.extend(HasManyQuery.ModelMixin, {
  comments: DS.hasMany('comment', {async: true})
});

var Comment = DS.Model.extend(HasManyQuery.ModelMixin, {
  post: DS.belongsTo('post', {async: true})
});

function initializeStore(adapter) {
  env = setupStore({
    adapter: adapter
  });

  env.registry.register('model:post', Post);
  env.registry.register('model:comment', Comment);
}

module("integration/query-has-many", function(hooks) {
  hooks.beforeEach(function() {
    var adapter = DS.RESTAdapter.extend(HasManyQuery.RESTAdapterMixin, {});
    initializeStore(adapter);
  });

  hooks.afterEach(function() {
    env = null;
  });

  test('Querying has-many relationship generates correct URL parameters', function (assert) {
    var ajaxCalledCount = 0;
    var requiredUrl = '';

    env.adapter.findRecord = function () {
      return resolve({post: {id: 5, links: {comments: "/posts/5/comments"}}});
    };

    env.adapter.ajax = function (url, method, options) {
      var queryString = $.param(options.data);
      assert.equal(url + '?' + queryString, requiredUrl, 'URL used to query has-many relationship is correct');
      ajaxCalledCount++;
      return resolve({comments: [{id: 1}]});
    };

    var done = assert.async();
    run(function () {
      env.store.findRecord('post', 5).then(function (post) {
        requiredUrl = '/posts/5/comments?page=1';
        return post.query('comments', {page: 1}).then(function () {
          requiredUrl = '/posts/5/comments?page=2';
          return post.query('comments', {page: 2});
        });
      }).then(function () {
        assert.equal(ajaxCalledCount, 2, 'Adapter ajax function was called to query has-many relationship');
        done();
      });
    });
  });

  test('Querying has-many relationship multiple times doesn\'t clear belongs-to-sticky association', function (assert) {
    Comment.reopen({
      post: HasManyQuery.belongsToSticky('post', {async: true})
    });

    env.adapter.findRecord = function () {
      return resolve({post: {id: 5, links: {comments: "/posts/5/comments"}}});
    };

    env.adapter.ajax = function (url, method, options) {
      var queryString = $.param(options.data);
      var page = queryString.match(/^.*(\d+)$/)[1];
      return resolve({comments: [{id: page * 2}, {id: page * 2 + 1}]});
    };

    var done = assert.async();
    run(function () {
      env.store.findRecord('post', 5).then(function (post) {
        return post.query('comments', {page: 1}).then(function (comments1) {
          var comments1Copy = comments1.slice(0);
          return post.query('comments', {page: 2}).then(function (comments2) {
            comments1Copy.forEach(function (comment) {
              assert.equal(comment.get('post.id'), 5, 'belongs-to association sticky after multiple has-many queries');
            });
            comments2.forEach(function (comment) {
              assert.equal(comment.get('post.id'), 5, 'belongs-to association correct');
            });
            done();
          });
        });
      });
    });
  });
});
<commit_msg>Removed jquery form test and added array to params function<commit_after>import { run } from '@ember/runloop';
import { resolve } from 'rsvp';
import DS from 'ember-data';
import { module, test } from 'qunit';
import { setupStore } from '../helpers/store';
import HasManyQuery from 'ember-data-has-many-query';

let env;

let Post = DS.Model.extend(HasManyQuery.ModelMixin, {
  comments: DS.hasMany('comment', {async: true})
});

let Comment = DS.Model.extend(HasManyQuery.ModelMixin, {
  post: DS.belongsTo('post', {async: true})
});

function queryParams(source) {
  var array = [];

  for(var key in source) {
    array.push(encodeURIComponent(key) + "=" + encodeURIComponent(source[key]));
  }

  return array.join("&");
}

function initializeStore(adapter) {
  env = setupStore({
    adapter: adapter
  });

  env.registry.register('model:post', Post);
  env.registry.register('model:comment', Comment);
}

module("integration/query-has-many", function(hooks) {
  hooks.beforeEach(function() {
    let adapter = DS.RESTAdapter.extend(HasManyQuery.RESTAdapterMixin, {});
    initializeStore(adapter);
  });

  hooks.afterEach(function() {
    env = null;
  });

  test('Querying has-many relationship generates correct URL parameters', function (assert) {
    let ajaxCalledCount = 0;
    let requiredUrl = '';

    env.adapter.findRecord = function () {
      return resolve({post: {id: 5, links: {comments: "/posts/5/comments"}}});
    };

    env.adapter.ajax = function (url, method, options) {
      let queryString = queryParams(options.data);
      assert.equal(url + '?' + queryString, requiredUrl, 'URL used to query has-many relationship is correct');
      ajaxCalledCount++;
      return resolve({comments: [{id: 1}]});
    };

    let done = assert.async();
    run(function () {
      env.store.findRecord('post', 5).then(function (post) {
        requiredUrl = '/posts/5/comments?page=1';
        return post.query('comments', {page: 1}).then(function () {
          requiredUrl = '/posts/5/comments?page=2';
          return post.query('comments', {page: 2});
        });
      }).then(function () {
        assert.equal(ajaxCalledCount, 2, 'Adapter ajax function was called to query has-many relationship');
        done();
      });
    });
  });

  test('Querying has-many relationship multiple times doesn\'t clear belongs-to-sticky association', function (assert) {
    Comment.reopen({
      post: HasManyQuery.belongsToSticky('post', {async: true})
    });

    env.adapter.findRecord = function () {
      return resolve({post: {id: 5, links: {comments: "/posts/5/comments"}}});
    };

    env.adapter.ajax = function (url, method, options) {
      let queryString = queryParams(options.data);
      let page = queryString.match(/^.*(\d+)$/)[1];
      return resolve({comments: [{id: page * 2}, {id: page * 2 + 1}]});
    };

    let done = assert.async();
    run(function () {
      env.store.findRecord('post', 5).then(function (post) {
        return post.query('comments', {page: 1}).then(function (comments1) {
          let comments1Copy = comments1.slice(0);
          return post.query('comments', {page: 2}).then(function (comments2) {
            comments1Copy.forEach(function (comment) {
              assert.equal(comment.get('post.id'), 5, 'belongs-to association sticky after multiple has-many queries');
            });
            comments2.forEach(function (comment) {
              assert.equal(comment.get('post.id'), 5, 'belongs-to association correct');
            });
            done();
          });
        });
      });
    });
  });
});
<|endoftext|>


<commit_before>"use strict";

describe("attributes/attributeforinstanceof", function () {

    describe("gpf.attributes.AttributeForInstanceOf", function () {

        var A,
            onInstanceOfA;

        before(function () {
            A = gpf.define({
                $class: "A",
                $abstract: true
            });
            onInstanceOfA = new gpf.attributes.AttributeForInstanceOf(A);
        });

        it("can't be used on a non-attribute class", function () {
            var exceptionCaught;
            try {
                debugger;
                gpf.define({
                    $class: "B",
                    $attributes: [onInstanceOfA]
                });
            } catch (e) {
                exceptionCaught = e;
            }
            assert(exceptionCaught);
        });

        it("can't be used on a member of an attribute class", function () {
            var exceptionCaught;
            try {
                gpf.define({
                    $class: "Attribute",
                    $extend: gpf.attributes.Attribute,

                    "[test]": [onInstanceOfA],
                    test: function () {}
                });
            } catch (e) {
                exceptionCaught = e;
            }
            assert(exceptionCaught);
        });

        describe("When used properly", function () {

            var AttributeForA;

            before(function () {
                AttributeForA = gpf.define({
                    $class: "AttributeForA",
                    $extend: gpf.attributes.Attribute,
                    $attributes: [onInstanceOfA]
                });
            });

            it("fails definition if used on the wrong class", function () {
                var exceptionCaught;
                try {
                    gpf.define({
                        $class: "NotAChildOfA",
                        $attributes: [AttributeForA]
                    })
                } catch (e) {
                    exceptionCaught = e;
                }
                assert(exceptionCaught);
            });

            it("accepts definition if used on direct child class", function () {
                var exceptionCaught;
                try {
                    gpf.define({
                        $class: "DirectChildOfA",
                        $extend: A,
                        $attributes: [AttributeForA]
                    })
                } catch (e) {
                    exceptionCaught = e;
                }
                assert(!exceptionCaught);
            });

            it("accepts definition if used on child class", function () {
                var exceptionCaught;
                try {
                    var DirectChildOfA = gpf.define({
                            $class: "DirectChildOfA",
                            $extend: A,
                            $attributes: [AttributeForA]
                        }),
                        SubChildOfA = gpf.define({
                            $class: "SubChildOfA",
                            $extend: DirectChildOfA,
                            $attributes: [AttributeForA]
                        })
                } catch (e) {
                    exceptionCaught = e;
                }
                assert(!exceptionCaught);
            });

        });
    });

});
<commit_msg>Linter fixes (#282)<commit_after>"use strict";

describe("attributes/attributeforinstanceof", function () {

    describe("gpf.attributes.AttributeForInstanceOf", function () {

        var A,
            onInstanceOfA;

        before(function () {
            A = gpf.define({
                $class: "A",
                $abstract: true
            });
            onInstanceOfA = new gpf.attributes.AttributeForInstanceOf(A);
        });

        it("can't be used on a non-attribute class", function () {
            var exceptionCaught;
            try {
                gpf.define({
                    $class: "B",
                    $attributes: [onInstanceOfA]
                });
            } catch (e) {
                exceptionCaught = e;
            }
            assert(exceptionCaught instanceof gpf.Error.ClassAttributeOnly);
        });

        it("can't be used on a member of an attribute class", function () {
            var exceptionCaught;
            try {
                gpf.define({
                    $class: "Attribute",
                    $extend: gpf.attributes.Attribute,

                    "[test]": [onInstanceOfA],
                    test: function () {}
                });
            } catch (e) {
                exceptionCaught = e;
            }
            assert(exceptionCaught);
        });

        describe("When used properly", function () {

            var AttributeForA;

            before(function () {
                AttributeForA = gpf.define({
                    $class: "AttributeForA",
                    $extend: gpf.attributes.Attribute,
                    $attributes: [onInstanceOfA]
                });
            });

            it("fails definition if used on the wrong class", function () {
                var exceptionCaught;
                try {
                    gpf.define({
                        $class: "NotAChildOfA",
                        $attributes: [AttributeForA]
                    });
                } catch (e) {
                    exceptionCaught = e;
                }
                assert(exceptionCaught);
            });

            it("accepts definition if used on direct child class", function () {
                var exceptionCaught;
                try {
                    gpf.define({
                        $class: "DirectChildOfA",
                        $extend: A,
                        $attributes: [AttributeForA]
                    });
                } catch (e) {
                    exceptionCaught = e;
                }
                assert(!exceptionCaught);
            });

            it("accepts definition if used on child class", function () {
                var exceptionCaught;
                try {
                    var DirectChildOfA = gpf.define({
                        $class: "DirectChildOfA",
                        $extend: A,
                        $attributes: [AttributeForA]
                    });
                    gpf.define({
                        $class: "SubChildOfA",
                        $extend: DirectChildOfA,
                        $attributes: [AttributeForA]
                    });
                } catch (e) {
                    exceptionCaught = e;
                }
                assert(!exceptionCaught);
            });

        });
    });

});
<|endoftext|>


<commit_before>describe("go.campaign.routing (models)", function() {
  var routing = go.campaign.routing,
      modelData = routing.testHelpers.modelData;

  var testHelpers = go.testHelpers,
      fakeServer = testHelpers.rpc.fakeServer;

  afterEach(function() {
    Backbone.Relational.store.reset();
  });

  describe(".CampaignRoutingModel", function() {
    var CampaignRoutingModel = routing.CampaignRoutingModel;

    var server;

    beforeEach(function() {
      server = fakeServer('/api/v1/go/api');
    });

    afterEach(function() {
      server.restore();
    });

    describe(".fetch", function() {
      it("should issue the correct api request", function() {
        var model = new CampaignRoutingModel({campaign_id: 'campaign1'});
        model.fetch();
        server.assertRequest('routing_table', ['campaign1']);
      });

      it("should update the model on the client side", function() {
        var model = new CampaignRoutingModel();
        model.fetch();
        server.respondWith(modelData);

        // Backbone.rpc adds an extra `_rpcId` attribute which isn't part of
        // our test model data. We need to exclude it for the assertion
        assert.deepEqual(_(model.toJSON()).omit('_rpcId'), {
          campaign_id: 'campaign1',
          channels: [{
            uuid: 'channel1',
            tag: ['apposit_sms', '*121#'],
            name: '*121#',
            description: 'Apposit Sms: *121#',
            endpoints: [{uuid: 'endpoint1', name: 'default'}]
          }, {
            uuid: 'channel2',
            tag: ['sigh_sms', '*131#'],
            name: '*131#',
            description: 'Sigh Sms: *131#',
            endpoints: [{uuid: 'endpoint2', name: 'default'}]
          }, {
            uuid: 'channel3',
            tag: ['larp_sms', '*141#'],
            name: '*141#',
            description: 'Larp Sms: *141#',
            endpoints: [{uuid: 'endpoint3', name: 'default'}]
          }],
          routing_blocks: [{
            uuid: 'routing-block1',
            type: 'keyword',
            name: 'keyword-routing-block',
            description: 'Keyword',
            channel_endpoints: [{uuid: 'endpoint4', name: 'default'}],
            conversation_endpoints: [{uuid: 'endpoint5', name: 'default'}]
          }, {
            uuid: 'routing-block2',
            type: 'keyword',
            name: 'keyword-routing-block',
            description: 'Keyword',
            channel_endpoints: [{uuid: 'endpoint6', name: 'default'}],
            conversation_endpoints: [{uuid: 'endpoint7', name: 'default'}]
          }],
          conversations: [{
            uuid: 'conversation1',
            type: 'bulk-message',
            name: 'bulk-message1',
            description: 'Some Bulk Message App',
            endpoints: [{uuid: 'endpoint8', name: 'default'}]
          }, {
            uuid: 'conversation2',
            type: 'bulk-message',
            name: 'bulk-message2',
            description: 'Some Other Bulk Message App',
            endpoints: [{uuid: 'endpoint9', name: 'default'}]
          }, {
            uuid: 'conversation3',
            type: 'js-app',
            name: 'js-app1',
            description: 'Some JS App',
            endpoints: [
              {uuid: 'endpoint10', name: 'default'},
              {uuid: 'endpoint11', name: 'sms'}]
          }],
          routing_entries: [{
            uuid: 'endpoint1-endpoint4',
            source: {uuid: 'endpoint1'},
            target: {uuid: 'endpoint4'}
          }]
        });
      });
    });

    describe(".save", function() {
      it("should issue the correct api request", function() {
        var model = new CampaignRoutingModel(modelData);
        model.save();
        server.assertRequest('update_routing_table', ['campaign1', modelData]);
      });
    });
  });
});
<commit_msg>Added a test to CampaignRoutingModel to ensure duplicate routing entries aren't made on successive calls to fetch()<commit_after>describe("go.campaign.routing (models)", function() {
  var routing = go.campaign.routing,
      modelData = routing.testHelpers.modelData;

  var testHelpers = go.testHelpers,
      fakeServer = testHelpers.rpc.fakeServer;

  afterEach(function() {
    Backbone.Relational.store.reset();
  });

  describe(".CampaignRoutingModel", function() {
    var CampaignRoutingModel = routing.CampaignRoutingModel;

    var server;

    beforeEach(function() {
      server = fakeServer('/api/v1/go/api');
    });

    afterEach(function() {
      server.restore();
    });

    describe(".fetch", function() {
      it("should issue the correct api request", function() {
        var model = new CampaignRoutingModel({campaign_id: 'campaign1'});
        model.fetch();
        server.assertRequest('routing_table', ['campaign1']);
      });

      it("should ensure duplicate routing entries aren't made", function() {
        var model = new CampaignRoutingModel({campaign_id: 'campaign1'});

        var assertEntries = function() {
          var entries = model.get('routing_entries');
          assert.equal(entries.size(), 1);
          assert.deepEqual(
            entries.map(function(e) { return e.id; }),
            ['endpoint1-endpoint4']);
        };

        model.fetch();
        server.respondWith(modelData);
        assertEntries();

        model.fetch();
        server.respondWith(modelData);
        assertEntries();
      });

      it("should update the model on the client side", function() {
        var model = new CampaignRoutingModel();
        model.fetch();
        server.respondWith(modelData);

        // Backbone.rpc adds an extra `_rpcId` attribute which isn't part of
        // our test model data. We need to exclude it for the assertion
        assert.deepEqual(_(model.toJSON()).omit('_rpcId'), {
          campaign_id: 'campaign1',
          channels: [{
            uuid: 'channel1',
            tag: ['apposit_sms', '*121#'],
            name: '*121#',
            description: 'Apposit Sms: *121#',
            endpoints: [{uuid: 'endpoint1', name: 'default'}]
          }, {
            uuid: 'channel2',
            tag: ['sigh_sms', '*131#'],
            name: '*131#',
            description: 'Sigh Sms: *131#',
            endpoints: [{uuid: 'endpoint2', name: 'default'}]
          }, {
            uuid: 'channel3',
            tag: ['larp_sms', '*141#'],
            name: '*141#',
            description: 'Larp Sms: *141#',
            endpoints: [{uuid: 'endpoint3', name: 'default'}]
          }],
          routing_blocks: [{
            uuid: 'routing-block1',
            type: 'keyword',
            name: 'keyword-routing-block',
            description: 'Keyword',
            channel_endpoints: [{uuid: 'endpoint4', name: 'default'}],
            conversation_endpoints: [{uuid: 'endpoint5', name: 'default'}]
          }, {
            uuid: 'routing-block2',
            type: 'keyword',
            name: 'keyword-routing-block',
            description: 'Keyword',
            channel_endpoints: [{uuid: 'endpoint6', name: 'default'}],
            conversation_endpoints: [{uuid: 'endpoint7', name: 'default'}]
          }],
          conversations: [{
            uuid: 'conversation1',
            type: 'bulk-message',
            name: 'bulk-message1',
            description: 'Some Bulk Message App',
            endpoints: [{uuid: 'endpoint8', name: 'default'}]
          }, {
            uuid: 'conversation2',
            type: 'bulk-message',
            name: 'bulk-message2',
            description: 'Some Other Bulk Message App',
            endpoints: [{uuid: 'endpoint9', name: 'default'}]
          }, {
            uuid: 'conversation3',
            type: 'js-app',
            name: 'js-app1',
            description: 'Some JS App',
            endpoints: [
              {uuid: 'endpoint10', name: 'default'},
              {uuid: 'endpoint11', name: 'sms'}]
          }],
          routing_entries: [{
            uuid: 'endpoint1-endpoint4',
            source: {uuid: 'endpoint1'},
            target: {uuid: 'endpoint4'}
          }]
        });
      });
    });

    describe(".save", function() {
      it("should issue the correct api request", function() {
        var model = new CampaignRoutingModel(modelData);
        model.save();
        server.assertRequest('update_routing_table', ['campaign1', modelData]);
      });
    });
  });
});
<|endoftext|>


<commit_before>// Cause: Reproductive Health

// We have a little templating "engine" that replaces placeholders.
/* eslint no-template-curly-in-string: 0 */

const data = {
  id: '4mC9rt2rb',
  about:
    '### About Tab for Global Health\n\n**Tab for Global Health** is proud to support [Partners In Health](https://www.pih.org/) and Dr. Paul Farmer’s legacy. With each tab opened, you are helping provide access to health care to those in need and strengthening public health systems all around the world. \n \nPartners In Health’s mission is to provide a preferential option for the poor in health care. By establishing long-term relationships with sister organizations based in settings of poverty, Partners In Health strives to achieve two overarching goals: to bring the benefits of modern medical science to those most in need of them and to serve as an antidote to despair.\n \nThey draw on the resources of the world’s leading medical and academic institutions and on the lived experience of the world’s most impoverished communities. At its root, their mission is both medical and moral. It is based on solidarity, rather than charity alone.\n \nWhen their patients are ill and have no access to care, their team of health professionals, scholars, and activists will do whatever it takes to make them well.\n \nPartners In Health has used a community-based model to provide health care and support for the last 30 years and now serves millions of patients across 12 countries.\n \n**Your tabs are making an impact**. The counter on your new tab page (with the medical bag icon) estimates how many in-home visits from a [community health worker](https://www.pih.org/programs/community-health-workers) your tabs have helped support.  Each counter increment represents raising ~1/50th of the money needed to support an in-home visit. Thank you for making a difference!',
  name: 'Global Health',
  isAvailableToSelect: false,
  icon: 'medical-bag',
  backgroundImageCategory: 'trees', // TODO: update with new collection
  charityId: 'ba4c2888-1d89-4c68-a47d-4f15f1445b6a',
  individualImpactEnabled: false,
  impactVisits: 131,
  landingPagePath: '/reproductive-health/',
  slug: 'reproductive-health',
  onboarding: {
    steps: [
      {
        title: '### Your tabs are doing great things',
        subtitle:
          'Now, every tab you open helps train and support community healthcare workers to provide at-home visits.\n\nTabbers like you are supporting critical nonprofit work all around the world. Thank you!\n\n',
        imgName: 'globalHealth/onboarding1.svg',
      },
      {
        title: '### Do more with your squad',
        subtitle:
          'With your squad, help even more people access safe health care!\n\nYou and your friends can team up to do more good.\n\n',
        imgName: 'globalHealth/onboarding2.svg',
      },
      {
        title: "### It doesn't cost you a thing",
        subtitle:
          "Ads on the new tab page raise money that we give to nonprofits. Most ads aren't good—but these ones are :)\n\n",
        imgName: 'globalHealth/onboarding3.svg',
      },
    ],
  },
  sharing: {
    title: '### **Get a friend on board**',
    subtitle:
      '##### Everyone can and _should_ make a difference: invite a friend to join in.\n\n',
    shareImage: 'globalHealth/sharing.svg',
    sentImage: 'globalHealth/sharing.svg',
    imgCategory: 'global-health',
    email: {
      image:
        'https://prod-tab2017-media.gladly.io/img/cause/globalHealth/emailInvite.jpg',
      title:
        'With Tab for Global Health, supporting healthcare access has never been easier.',
      about:
        '
Tab for Global Health turns your web browser into a force for good. With each tab opened, ad revenue supports community healthcare training through Partners In Health. This training helps ensure the people in need of care will receive it, long term.
\n\n',
      faq:
        'We’re partnered with Partners In Health to ensure the money raised will directly support their mission of providing high-quality health care globally to those who need it.',
      sendgridEmailTemplateId: 'd-c925270aca954ff3b4c05f436cb52b54',
    },
    sendgridEmailTemplateId: 'd-c925270aca954ff3b4c05f436cb52b54',
    facebookButtonTitle:
      'People around the world are struggling to access safe and reliable healthcare, but this is a problem we can all help solve. Now, with Tab for Global Health, every tab I open helps raise money for Partners In Health to train community healthcare workers. Check it out - it’s free!',
    twitterButtonTitle:
      'People around the world are struggling to access quality health care, but this is a problem we can solve. Now, with Tab for Global Health, every tab I open raises money for Partners In Health to train community healthcare workers. Check it out - it’s free!',
    redditButtonTitle: 'Help provide quality healthcare around the world',
    tumblrTitle: 'Help provide quality healthcare around the world ',
    tumblrCaption:
      'Every time I open a new tab with Tab for Global Health I am raising money to train healthcare workers through Partners In Health. Join today for free! ',
  },
  // TODO
  squads: {
    currentMissionAlert: 'TODO',
    currentMissionDetails: 'TODO',
    currentMissionStep2: 'TODO',
    currentMissionStep3: 'TODO',
    currentMissionSummary: 'TODO',
    impactCounterText: 'TODO',
    missionCompleteAlert: 'TODO',
    missionCompleteDescription: 'TODO',
    missionCompleteSubtitle: 'TODO',
    squadCounterText: 'TODO',
    squadInviteTemplateId: 'TODO',
  },
  theme: {
    primaryColor: '#FB5050',
    secondaryColor: '#29BEBA',
  },
}

export default data
<commit_msg>Update images<commit_after>// Cause: Reproductive Health

// We have a little templating "engine" that replaces placeholders.
/* eslint no-template-curly-in-string: 0 */

const data = {
  id: '4mC9rt2rb',
  about:
    '### About Tab for Global Health\n\n**Tab for Global Health** is proud to support [Partners In Health](https://www.pih.org/) and Dr. Paul Farmer’s legacy. With each tab opened, you are helping provide access to health care to those in need and strengthening public health systems all around the world. \n \nPartners In Health’s mission is to provide a preferential option for the poor in health care. By establishing long-term relationships with sister organizations based in settings of poverty, Partners In Health strives to achieve two overarching goals: to bring the benefits of modern medical science to those most in need of them and to serve as an antidote to despair.\n \nThey draw on the resources of the world’s leading medical and academic institutions and on the lived experience of the world’s most impoverished communities. At its root, their mission is both medical and moral. It is based on solidarity, rather than charity alone.\n \nWhen their patients are ill and have no access to care, their team of health professionals, scholars, and activists will do whatever it takes to make them well.\n \nPartners In Health has used a community-based model to provide health care and support for the last 30 years and now serves millions of patients across 12 countries.\n \n**Your tabs are making an impact**. The counter on your new tab page (with the medical bag icon) estimates how many in-home visits from a [community health worker](https://www.pih.org/programs/community-health-workers) your tabs have helped support.  Each counter increment represents raising ~1/50th of the money needed to support an in-home visit. Thank you for making a difference!',
  name: 'Global Health',
  isAvailableToSelect: false,
  icon: 'medical-bag',
  backgroundImageCategory: 'trees', // TODO: update with new collection
  charityId: 'ba4c2888-1d89-4c68-a47d-4f15f1445b6a',
  individualImpactEnabled: false,
  impactVisits: 131,
  landingPagePath: '/reproductive-health/',
  slug: 'reproductive-health',
  onboarding: {
    steps: [
      {
        title: '### Your tabs are doing great things',
        subtitle:
          'Now, every tab you open helps train and support community healthcare workers to provide at-home visits.\n\nTabbers like you are supporting critical nonprofit work all around the world. Thank you!\n\n',
        imgName: 'reproductiveHealth/onboarding1.svg',
      },
      {
        title: '### Do more with your squad',
        subtitle:
          'With your squad, help even more people access safe health care!\n\nYou and your friends can team up to do more good.\n\n',
        imgName: 'reproductiveHealth/onboarding2.svg',
      },
      {
        title: "### It doesn't cost you a thing",
        subtitle:
          "Ads on the new tab page raise money that we give to nonprofits. Most ads aren't good—but these ones are :)\n\n",
        imgName: 'reproductiveHealth/onboarding3.svg',
      },
    ],
  },
  sharing: {
    title: '### **Get a friend on board**',
    subtitle:
      '##### Everyone can and _should_ make a difference: invite a friend to join in.\n\n',
    shareImage: 'sharing.svg',
    sentImage: 'sharing.svg',
    imgCategory: 'global-health',
    email: {
      image: 'https://prod-tab2017-media.gladly.io/img/cause/emailInvite.jpg',
      title:
        'With Tab for Global Health, supporting healthcare access has never been easier.',
      about:
        '
Tab for Global Health turns your web browser into a force for good. With each tab opened, ad revenue supports community healthcare training through Partners In Health. This training helps ensure the people in need of care will receive it, long term.
\n\n',
      faq:
        'We’re partnered with Partners In Health to ensure the money raised will directly support their mission of providing high-quality health care globally to those who need it.',
      sendgridEmailTemplateId: 'd-c925270aca954ff3b4c05f436cb52b54',
    },
    sendgridEmailTemplateId: 'd-c925270aca954ff3b4c05f436cb52b54',
    facebookButtonTitle:
      'People around the world are struggling to access safe and reliable healthcare, but this is a problem we can all help solve. Now, with Tab for Global Health, every tab I open helps raise money for Partners In Health to train community healthcare workers. Check it out - it’s free!',
    twitterButtonTitle:
      'People around the world are struggling to access quality health care, but this is a problem we can solve. Now, with Tab for Global Health, every tab I open raises money for Partners In Health to train community healthcare workers. Check it out - it’s free!',
    redditButtonTitle: 'Help provide quality healthcare around the world',
    tumblrTitle: 'Help provide quality healthcare around the world ',
    tumblrCaption:
      'Every time I open a new tab with Tab for Global Health I am raising money to train healthcare workers through Partners In Health. Join today for free! ',
  },
  // TODO
  squads: {
    currentMissionAlert: 'TODO',
    currentMissionDetails: 'TODO',
    currentMissionStep2: 'TODO',
    currentMissionStep3: 'TODO',
    currentMissionSummary: 'TODO',
    impactCounterText: 'TODO',
    missionCompleteAlert: 'TODO',
    missionCompleteDescription: 'TODO',
    missionCompleteSubtitle: 'TODO',
    squadCounterText: 'TODO',
    squadInviteTemplateId: 'TODO',
  },
  theme: {
    primaryColor: '#FB5050',
    secondaryColor: '#29BEBA',
  },
}

export default data
<|endoftext|>


<commit_before>var Class = require('std/Class')

module.exports = Class(function() {

	this.init = function(x, y, w, h) {
		this.x = x
		this.y = y
		this._x2 = x + w
		this._y2 = y + h
	}

	this.pad = function(amount) {
		this.x -= amount
		this.y -= amount
		this._x2 += amount
		this._y2 += amount
		return this
	}

	this.containsPoint = function(point) {
		log(this.x,' < ', point.x, ' && ', point.x, ' < ', this._x2)
		log(this.y,' < ', point.y,' && ',point.y,' < ',this._y2)
		return this.x < point.x && point.x < this._x2
			&& this.y < point.y && point.y < this._y2
	}

})<commit_msg>Remove log statements from rect<commit_after>var Class = require('std/Class')

module.exports = Class(function() {

	this.init = function(x, y, w, h) {
		this.x = x
		this.y = y
		this._x2 = x + w
		this._y2 = y + h
	}

	this.pad = function(amount) {
		this.x -= amount
		this.y -= amount
		this._x2 += amount
		this._y2 += amount
		return this
	}

	this.containsPoint = function(point) {
		return this.x < point.x && point.x < this._x2
			&& this.y < point.y && point.y < this._y2
	}

})<|endoftext|>