<commit_before>Login = {};

var serviceOptions = {
  "facebook": { requestPermissions: [ 'email', 'user_location', 'user_birthday', 'user_website', 'user_work_history' ] },
  "github": { requestPermissions: [ 'user:email' /* XXX not working??? */ ] },
  "twitter": { requestPermissions: [ /* no permission available */ ] }
}

Login.init = function() {
  observe();
}

/* OBSERVE Login State */

var observe = function() {
  var hasLoggedInBefore = false;

  Deps.autorun(function() {
    if (!Subscriptions.ready())
      return; // wait until subscriptions are ready

    if (Meteor.userId()) { // user logged in
      hasLoggedInBefore = true;
      Deps.nonreactive(loggedIn);
    }

    if (!Meteor.userId() && hasLoggedInBefore) {  // user logged out
      hasLoggedInBefore = false;
      Deps.nonreactive(loggedOut);
    }
  });
}

/* LOGIN EVENT handlers */

// when user is logged in by filling in its credentials
var manuallyLoggedIn = function() {
  /* do nothing */
}

// when user becomes logged in
var loggedIn = function() {

  // log
  GAnalytics.event("LoginService", "login", "automatically");

  // XXX maybe we can do this on the server side, because
  // meteor introduces a function called Accounts.validateLoginAttempt()
  checkDuplicateIdentity();
  checkInvitation();
  checkAccess();

  // if a redirectUrl is present, redirect to that url
  // otherwise if also no route is setted to the hackers list
  var redirectUrl = Session.get('redirectUrl');
  var currentRoute = Router.current().route.name;

  if (redirectUrl) {
    Session.set('redirectUrl', null);
    Router.go(redirectUrl);
  } else if(currentRoute === 'frontpage') {
    goToEntryPage();
  } else { // reload page to trigger route actions again
    Router.reload();
  }

}

// when user becomes logged out
var loggedOut = function() {
  /* empty */
}

// which page should be loaded for logged in users which enter the site
goToEntryPage = function() {
  Router.go('highlights');
}

/* ACCOUNT & ACCESS & INVITATIONS */

Template.main.events({
  "click #requestMergeDuplicateAccount .close": function() {
    Session.set('requestMergeDuplicateAccount', false);
  }
});
UI.registerHelper('previousLoginSession', function() {
  return Session.get('previousLoginSession');
});
UI.registerHelper('invitationLimitReached', function() {
  return Session.get('invitationLimitReached');
});
UI.registerHelper('tellUsMore', function() {
  return Meteor.user().isIncompleteProfile;
});
UI.registerHelper('isInvited', function() {
  return checkInvited();
});
UI.registerHelper('isVerifiedEmail', function() {
  return verifiedEmail(); // user's profile email is verified
});
UI.registerHelper('isUnverifiedEmail', function() {
  return isUnverifiedEmail(); // user's profile email is not verified
});

// check if there is an other existing user account
// that probably match the current user idenity
// we check that by using the browser persistent storage
checkDuplicateIdentity = function() {

  var currentService = amplify.store('currentLoginService');
  var previousSession = amplify.store('previousLoginSession');

  var isRecentlyCreated = new Date().getTime() - Meteor.user().createdAt.getTime() < 2*60*1000; //5min
  var isOtherService = previousSession && currentService && previousSession.service != currentService;
  var isOtherAccount = previousSession && previousSession.userId != Meteor.userId();

  // when this is a new account (created in the last 2 minutes), then we check
  // if there are previously login session with an other account with different service
  // if so we notify the user that he has possible 2 account and we request to merge them
  var requestMerge = isRecentlyCreated && isOtherService && isOtherAccount;

  Session.set('previousLoginSession', previousSession);
  Session.set('requestMergeDuplicateAccount', requestMerge);

  // store the current login session in persistent browser storage,
  // so we can check for duplicate idenity next time
  if (currentService) {
    amplify.store("previousLoginSession", {
      userId: Meteor.userId(),
      service: currentService
    });
  }
}

// when user isn't yet allowed to enter the site
// check if he has signed up with a valid invite code
checkInvitation = function() {

  var phrase = Session.get('invitationPhrase');
  var broadcastUser = Users.findOne({invitationPhrase: phrase});

  if (phrase) {

    // make a server call to check the invitation
    Meteor.call('verifyInvitation', phrase, function(err) {

      if (err && err.reason === 'limit') {

        Router.reload();

        // show invitation limit reached message
        Session.set('invitationLimitReached', true);
        Meteor.setTimeout(function() {
          Session.set('invitationLimitReached', false);
        }, 5 * 60 * 1000);

        // log to google analytics
        if (broadcastUser)
          GAnalytics.event('Invitations', 'limit reached for user', broadcastUser._id);

      } else if (err) {

        Router.reload();

        log("Error", err);

        // log to google analytics
        GAnalytics.event('Invitations', 'invalid phrase', phrase);

      } else { //on success

        goToEntryPage();

        // log to google analytics
        if (broadcastUser)
          GAnalytics.event('Invitations', 'invited by user', broadcastUser._id);
      }

      // clean
      Session.set('invitationPhrase', null);

    });

  } else {

    // clean
    Session.set('invitationPhrase', null);
  }
};

// user let us know that he has filled in his profile
// check on server if this is correct
checkCompletedProfile = function() { /* GLOBAL, called from hacker.js */
  if (Meteor.user().isIncompleteProfile) {
    exec(function() {
      Meteor.call('requestProfileCompleted', function(err) {
        if (err) {
          Router.reload();
          log(err);
        } else {
          goToEntryPage();
        }
      });
    });
  }
}

// new users have no access to the site until their profile is complete
// observe if the fields email and name are filled in, after saving
// also the user must have filled in a verified e-mailaddress
checkAccess = function() { /* GLOBAL, called from router.js */
  exec(function() {
    var user = Meteor.user();
    var profile = user.profile;
    if (user.isAccessDenied && !user.isIncompleteProfile && checkInvited() && verifiedEmail()) {
      Meteor.call('requestAccess', function(err) {
        if (err) {
          Router.reload();
          log(err);
        } else {
          goToEntryPage();
        }
      });
    }
  });
};

// check if user is invited
var checkInvited = function() { //GLOBAL, used in hacker.js
  return !Meteor.user().isUninvited;
}

// check if user's profile e-mail address is verified
var verifiedEmail = function() { //GLOBAL, used in hacker.js
  var user = Meteor.user();
  return !!_.findWhere(user.emails, {address: user.profile.email, verified: true});
}

// check if user's profile e-mail address is not verified
var isUnverifiedEmail = function() {
  return Meteor.user().profile.email && !verifiedEmail();
}

/* LOGIN functionality */

UI.registerHelper('serviceLoginError', function() {
  return Session.get('serviceLoginError');
});

// after user tries to login, check if an error occured
var loginCallback = function(err) {
  if (err) {

    // log
    GAnalytics.event("LoginService", "login failure");

    // on error
    var message = "
Something went wrong...
Please try again or email us.";

    // emailadres is in use by another user
    if (err.reason === "duplicateEmail")
      message = "
Oopsy!
Please try one of the other services!";

    if (err.reason === "city unmatch") {
      var userCity = err.details;
      var cityUrl = Url.replaceCity(userCity);
      var cityDomain = Url.hostname(cityUrl);
      message = '
oopsy!
'+cityDomain+' looks more like your home ;)';
    }

    Session.set('serviceLoginError', message);
    Meteor.setTimeout(function() { Session.set('serviceLoginError', false); }, 12000);
    log(err);

  } else {
    // on success
    manuallyLoggedIn();
  }
}

// login by using a external service
var loginWithService = function(event) {
  var $elm = $(event.currentTarget);
  var service = $elm.data('service');
  var options = serviceOptions[service];
  var Service = capitaliseFirstLetter(service);

  // log
  GAnalytics.event("LoginService", "login", service);

  // set used service as cookie
  amplify.store('currentLoginService', service);

  // login
  Meteor["loginWith"+Service](options, loginCallback);
}

// log out the current user
var logout = function() {

  // log
  GAnalytics.event("LoginService", "logout");

  // first redirect to frontpage to make sure there are no helpers
  // active that making use of the user session information
  // this prevent from errors in the console
  Router.go('frontpage');
  Deps.flush();
  Meteor.setTimeout(function() {
    Meteor.logout();
  }, 200);
}

// bind the sign up buttons to the corresponding actions
Template.main.events({
  "click .signupService": loginWithService
});

// bind the sign out button to the sign out action
Template.main.events({
  "click #signOutButton": logout
});

/* ADD SERVICES to my profile */

// add an external service to current user's account
var global = this;
var _addService = function(service, options, onSuccessCallback) {
  var Service = window[capitaliseFirstLetter(service)];

  // request a token from the external service
  Service.requestCredential(options, function(token, more) {
    var secret = OAuth._retrieveCredentialSecret(token);

    // send the token to our server-side method, which will handle
    // updating the user with the new service information
    Meteor.call("addServiceToUser", token, secret, service, function(err, res) {
      if (err) {
        if (err.reason === "duplicateEmail") {
          // emailadres is in use by another user
          Session.set('isAddServiceError_'+service, true);
          Meteor.setTimeout(function() { Session.set('isAddServiceError_'+service, false); }, 10000);
        } else {
          log(err);
        }

        // log
        GAnalytics.event("LoginService", "link failure", service);
      } else { //success

        if(_.isFunction(onSuccessCallback))
          onSuccessCallback();

        // log
        GAnalytics.event("LoginService", "link service", service);
      }
    });
  });
}

// remove an external service from user's account
var _removeService = function(service) {
  Meteor.call("removeServiceFromUser", service, function(err, res) {
    if (err)
      throw new Meteor.Error(500, err.reason);
    else
      // log
      GAnalytics.event("LoginService", "unlink service", service);
  });
}

// user toggles an external service
// add or remove the service from user's account
toggleService = function (event, onSuccessCallback) {
  var $elm = $(event.currentTarget);
  var service = $elm.data('service');
  var options = serviceOptions[service];
  var isLinked = !!Meteor.user().profile.social[service];

  isLinked ? _removeService(service) : _addService(service, options, onSuccessCallback);
}

<commit_msg>Redirect merged user to correct city.<commit_after>Login = {};

var serviceOptions = {
  "facebook": { requestPermissions: [ 'email', 'user_location', 'user_birthday', 'user_website', 'user_work_history' ] },
  "github": { requestPermissions: [ 'user:email' /* XXX not working??? */ ] },
  "twitter": { requestPermissions: [ /* no permission available */ ] }
}

Login.init = function() {
  observe();
}

/* OBSERVE Login State */

var observe = function() {
  var hasLoggedInBefore = false;

  Deps.autorun(function() {
    if (!Subscriptions.ready())
      return; // wait until subscriptions are ready

    if (Meteor.userId()) { // user logged in
      hasLoggedInBefore = true;
      Deps.nonreactive(loggedIn);
    }

    if (!Meteor.userId() && hasLoggedInBefore) {  // user logged out
      hasLoggedInBefore = false;
      Deps.nonreactive(loggedOut);
    }
  });
}

/* LOGIN EVENT handlers */

// when user is logged in by filling in its credentials
var manuallyLoggedIn = function() {
  /* do nothing */
}

// when user becomes logged in
var loggedIn = function() {

  // log
  GAnalytics.event("LoginService", "login", "automatically");

  // XXX maybe we can do this on the server side, because
  // meteor introduces a function called Accounts.validateLoginAttempt()
  checkDuplicateIdentity();
  checkInvitation();
  checkAccess();

  // if a redirectUrl is present, redirect to that url
  // otherwise if also no route is setted to the hackers list
  var redirectUrl = Session.get('redirectUrl');
  var currentRoute = Router.current().route.name;

  if (redirectUrl) {
    Session.set('redirectUrl', null);
    Router.go(redirectUrl);
  } else if(currentRoute === 'frontpage') {
    goToEntryPage();
  } else { // reload page to trigger route actions again
    Router.reload();
  }

}

// when user becomes logged out
var loggedOut = function() {
  /* empty */
}

// which page should be loaded for logged in users which enter the site
goToEntryPage = function() {
  Router.go('highlights');
}

/* ACCOUNT & ACCESS & INVITATIONS */

Template.main.events({
  "click #requestMergeDuplicateAccount .close": function() {
    Session.set('requestMergeDuplicateAccount', false);
  }
});
UI.registerHelper('previousLoginSession', function() {
  return Session.get('previousLoginSession');
});
UI.registerHelper('invitationLimitReached', function() {
  return Session.get('invitationLimitReached');
});
UI.registerHelper('tellUsMore', function() {
  return Meteor.user().isIncompleteProfile;
});
UI.registerHelper('isInvited', function() {
  return checkInvited();
});
UI.registerHelper('isVerifiedEmail', function() {
  return verifiedEmail(); // user's profile email is verified
});
UI.registerHelper('isUnverifiedEmail', function() {
  return isUnverifiedEmail(); // user's profile email is not verified
});

// check if there is an other existing user account
// that probably match the current user idenity
// we check that by using the browser persistent storage
checkDuplicateIdentity = function() {

  var currentService = amplify.store('currentLoginService');
  var previousSession = amplify.store('previousLoginSession');

  var isRecentlyCreated = new Date().getTime() - Meteor.user().createdAt.getTime() < 2*60*1000; //5min
  var isOtherService = previousSession && currentService && previousSession.service != currentService;
  var isOtherAccount = previousSession && previousSession.userId != Meteor.userId();

  // when this is a new account (created in the last 2 minutes), then we check
  // if there are previously login session with an other account with different service
  // if so we notify the user that he has possible 2 account and we request to merge them
  var requestMerge = isRecentlyCreated && isOtherService && isOtherAccount;

  Session.set('previousLoginSession', previousSession);
  Session.set('requestMergeDuplicateAccount', requestMerge);

  // store the current login session in persistent browser storage,
  // so we can check for duplicate idenity next time
  if (currentService) {
    amplify.store("previousLoginSession", {
      userId: Meteor.userId(),
      service: currentService
    });
  }
}

// when user isn't yet allowed to enter the site
// check if he has signed up with a valid invite code
checkInvitation = function() {

  var phrase = Session.get('invitationPhrase');
  var broadcastUser = Users.findOne({invitationPhrase: phrase});

  if (phrase) {

    // make a server call to check the invitation
    Meteor.call('verifyInvitation', phrase, function(err) {

      if (err && err.reason === 'limit') {

        Router.reload();

        // show invitation limit reached message
        Session.set('invitationLimitReached', true);
        Meteor.setTimeout(function() {
          Session.set('invitationLimitReached', false);
        }, 5 * 60 * 1000);

        // log to google analytics
        if (broadcastUser)
          GAnalytics.event('Invitations', 'limit reached for user', broadcastUser._id);

      } else if (err) {

        Router.reload();

        log("Error", err);

        // log to google analytics
        GAnalytics.event('Invitations', 'invalid phrase', phrase);

      } else { //on success

        goToEntryPage();

        // log to google analytics
        if (broadcastUser)
          GAnalytics.event('Invitations', 'invited by user', broadcastUser._id);
      }

      // clean
      Session.set('invitationPhrase', null);

    });

  } else {

    // clean
    Session.set('invitationPhrase', null);
  }
};

// user let us know that he has filled in his profile
// check on server if this is correct
checkCompletedProfile = function() { /* GLOBAL, called from hacker.js */
  if (Meteor.user().isIncompleteProfile) {
    exec(function() {
      Meteor.call('requestProfileCompleted', function(err) {
        if (err) {
          Router.reload();
          log(err);
        } else {
          goToEntryPage();
        }
      });
    });
  }
}

// new users have no access to the site until their profile is complete
// observe if the fields email and name are filled in, after saving
// also the user must have filled in a verified e-mailaddress
checkAccess = function() { /* GLOBAL, called from router.js */
  exec(function() {
    var user = Meteor.user();
    var profile = user.profile;
    if (user.isAccessDenied && !user.isIncompleteProfile && checkInvited() && verifiedEmail()) {
      Meteor.call('requestAccess', function(err) {
        if (err) {
          Router.reload();
          log(err);
        } else {
          goToEntryPage();
        }
      });
    }
  });
};

// check if user is invited
var checkInvited = function() { //GLOBAL, used in hacker.js
  return !Meteor.user().isUninvited;
}

// check if user's profile e-mail address is verified
var verifiedEmail = function() { //GLOBAL, used in hacker.js
  var user = Meteor.user();
  return !!_.findWhere(user.emails, {address: user.profile.email, verified: true});
}

// check if user's profile e-mail address is not verified
var isUnverifiedEmail = function() {
  return Meteor.user().profile.email && !verifiedEmail();
}

/* LOGIN functionality */

UI.registerHelper('serviceLoginError', function() {
  return Session.get('serviceLoginError');
});

// after user tries to login, check if an error occured
var loginCallback = function(err) {
  if (err) {

    // log
    GAnalytics.event("LoginService", "login failure");

    // on error
    var message = "
Something went wrong...
Please try again or email us.";

    // emailadres is in use by another user
    if (err.reason === "duplicateEmail")
      message = "
Oopsy!
Please try one of the other services!";

    if (err.reason === "city unmatch") {
      var userCity = err.details;
      var cityUrl = Url.replaceCity(userCity);
      var cityDomain = Url.hostname(cityUrl);
      message = '
oopsy!
'+cityDomain+' looks more like your home ;)';
    }

    Session.set('serviceLoginError', message);
    Meteor.setTimeout(function() { Session.set('serviceLoginError', false); }, 12000);
    log(err);

  } else {

    // when the merged user account is located in an other city
    // we have to redirect the subdomain
    if (Meteor.user().city !== Session.get('currentCity'))
      Router.goToCity(Meteor.user().city);

    // on success
    manuallyLoggedIn();
  }
}

// login by using a external service
var loginWithService = function(event) {
  var $elm = $(event.currentTarget);
  var service = $elm.data('service');
  var options = serviceOptions[service];
  var Service = capitaliseFirstLetter(service);

  // log
  GAnalytics.event("LoginService", "login", service);

  // set used service as cookie
  amplify.store('currentLoginService', service);

  // login
  Meteor["loginWith"+Service](options, loginCallback);
}

// log out the current user
var logout = function() {

  // log
  GAnalytics.event("LoginService", "logout");

  // first redirect to frontpage to make sure there are no helpers
  // active that making use of the user session information
  // this prevent from errors in the console
  Router.go('frontpage');
  Deps.flush();
  Meteor.setTimeout(function() {
    Meteor.logout();
  }, 200);
}

// bind the sign up buttons to the corresponding actions
Template.main.events({
  "click .signupService": loginWithService
});

// bind the sign out button to the sign out action
Template.main.events({
  "click #signOutButton": logout
});

/* ADD SERVICES to my profile */

// add an external service to current user's account
var global = this;
var _addService = function(service, options, onSuccessCallback) {
  var Service = window[capitaliseFirstLetter(service)];

  // request a token from the external service
  Service.requestCredential(options, function(token, more) {
    var secret = OAuth._retrieveCredentialSecret(token);

    // send the token to our server-side method, which will handle
    // updating the user with the new service information
    Meteor.call("addServiceToUser", token, secret, service, function(err, res) {
      if (err) {
        if (err.reason === "duplicateEmail") {
          // emailadres is in use by another user
          Session.set('isAddServiceError_'+service, true);
          Meteor.setTimeout(function() { Session.set('isAddServiceError_'+service, false); }, 10000);
        } else {
          log(err);
        }

        // log
        GAnalytics.event("LoginService", "link failure", service);
      } else { //success

        // when the merged user account is located in an other city
        // we have to redirect the subdomain
        if (Meteor.user().city !== Session.get('currentCity'))
          Router.goToCity(Meteor.user().city);

        if(_.isFunction(onSuccessCallback))
          onSuccessCallback();

        // log
        GAnalytics.event("LoginService", "link service", service);
      }
    });
  });
}

// remove an external service from user's account
var _removeService = function(service) {
  Meteor.call("removeServiceFromUser", service, function(err, res) {
    if (err)
      throw new Meteor.Error(500, err.reason);
    else
      // log
      GAnalytics.event("LoginService", "unlink service", service);
  });
}

// user toggles an external service
// add or remove the service from user's account
toggleService = function (event, onSuccessCallback) {
  var $elm = $(event.currentTarget);
  var service = $elm.data('service');
  var options = serviceOptions[service];
  var isLinked = !!Meteor.user().profile.social[service];

  isLinked ? _removeService(service) : _addService(service, options, onSuccessCallback);
}

<|endoftext|>


<commit_before>'use strict';
var pg = require('pg');
var conString = "postgres://" + process.env.POSTGRES + "/postgres";

/**
 * Get tree data for a single tree (profile view)
 */
exports.getTreeData = function(req, res) {
  var treeid = req.params.treeId;
  pg.connect(conString, function(err, client, done) {
    var selectMessages = 'SELECT tree.name, tree.treeid, q.qspecies, tree.plotsize, tree.qcaretaker, tree.plantdate, l.latitude, l.longitude, image.imageurl, image.imagewidth, image.imageheight, image.imagetype from qspecies q JOIN tree ON (q.qspeciesid = tree.qspeciesid) JOIN "location" l ON (l.locationid = tree.locationid) JOIN image ON (q.qspeciesid = image.qspeciesid) WHERE treeid = $1;';
    client.query(selectMessages, [treeid], function(error, results) {
      done();
      if (error) {
        console.log(error, 'THERE WAS AN ERROR');
      }else{
        res.json(results.rows[0]);
      }
    });
  });
};

/**
 * Get tree data for 250 trees (list view)
 */
exports.getAll = function(req, res) {
  pg.connect(conString, function(err, client, done) {
    console.log(err);
    var selectTrees = 'SELECT tree.name, tree.treeid, q.qspecies, l.latitude, l.longitude, thumbnail.url, thumbnail.width, thumbnail.height, thumbnail.contenttype FROM qspecies q JOIN tree ON (q.qspeciesid = tree.qspeciesid) JOIN "location" l ON (l.locationid = tree.locationid) JOIN thumbnail ON (q.qspeciesid = thumbnail.qspeciesid) LIMIT 250;';
    client.query(selectTrees, function(error, results) {
    }, function(error, results) {
      done();
      if(error) {
        console.log('Error is ', error);
      }
      res.json(results.rows);
    });
  });
};

/**
 * Gets tree's messages from database and sends it to client. Request is expecting a treeId.
 * @param req
 * @param res
 */
exports.getMessagesForTree = function(req, res) {
  var treeid = req.params.treeid;
  console.log('in getMessageForTree and treeid is', treeid);
  pg.connect(conString, function(err, client, done) {
    console.log(err);
    var selectMessages = 'SELECT message.message, message.treeid, message.username, message.messageid FROM message  WHERE treeid = $1 LIMIT 100;';
    client.query(selectMessages, [treeid], function(error, results) {
      res.json(results.rows);
    });
    done();
  });
};

/**
 * Gets messages from users and sends it to the database. Request is expecting an username.
 * @param req
 * @param res
 */
exports.getMessagesForUsers = function(req, res) {
  var username = req.params.userid;
  console.log('in getMessageForUsers and username is', username);
  pg.connect(conString, function(err, client, done) {
    console.log(err);
    var selectMessages = 'SELECT message.message, message.treeid, message.username, message.messageid FROM message WHERE username = $1 LIMIT 100;';
    client.query(selectMessages, [username], function(error, results) {
      console.log('results.rows is ', results.rows);
      res.json(results.rows);
    });
    done();
  });
};

/**
 * Gets messages from both users or trees and uploads them to the database. Request expecting treeName or userName,
 * and a treeId.
 * @param req
 * @param res
 */
//
exports.postMessageFromUser = function(req, res) {
  console.log('got into postMessageFromUser');
  var username = req.body.username;
  var message = req.body.message;
  var treeid = req.body.treeid;
  console.log(username, message, treeid);
  pg.connect(conString, function(err, client, done) {
    if(err) { console.log('error is', err); }
    else {
      var insertMessages = 'INSERT INTO message (message, username, treeid) values ($1, $2, $3) RETURNING *;';
      client.query(insertMessages, [message, username, treeid], function (error, results) {
        console.log('postMessageFromUser result is ', results.rows);
        res.json(results.rows);
        done();
      });
    }
  });
};

/**
 * Search tree return 250 results matching the search params. It takes a search param, and an offset. If offset is not
 * provided it will default to 0.
 * @param req
 * @param res
 */
exports.searchTrees = function(req, res) {
  var search = req.search;
  var offset = req.offset || 0;
  var searchString = typeof search === 'string' ? '%' + search + '%' : 'do not use';
  var searchNum = typeof search === 'number' ? search : 0;
  pg.connect(conString, function(err, client, done) {
    console.log('in search trees');
    console.log(err);
    var selectTrees = 'SELECT tree.name, q.qspecies, l.latitude, l.longitude, thumbnail.url, thumbnail.width, ' +
      'thumbnail.height, thumbnail.contenttype FROM qspecies q JOIN tree ON (q.qspeciesid = tree.qspeciesid) JOIN ' +
      '"location" l ON (l.locationid = tree.locationid) JOIN thumbnail ON (q.qspeciesid = thumbnail.qspeciesid) WHERE ' +
      'tree.treeid = $1 OR tree.name LIKE $2 OR q.qspecies = $2 OR q.qspeciesid = $1 LIMIT 250 OFFSET $3;';
    client.query(selectTrees, [searchNum, searchString, offset], function(error, results){
      console.log('error', error);
      //console.log('results', results);
      console.log(results.rows);
      done();
    });
  });
};

/**
 * This function expects a treeid to insert messages. The userid will be saved with the value of -1 which means that
 * the message was sent by the tree.
 * @param req
 * @param res
 */
exports.insertMessagesFromTrees = function(req, res) {
  console.log('got into insertMessagesFromTrees is ');
  var treeid = req.body.treeid;
  var userid = -1;
  var message = req.body.message;
  console.log(treeid, userid, message);
  pg.connect(conString, function(err, client, done) {
    console.log(err);
    var selectMessages = 'INSERT INTO message (message, treeid, userid) values($1, $2, $3)';
    client.query(selectMessages, [message, treeid, userid], function(error, results) {
      console.log('results is ', results);
      res.send(results);
      done();
    });

  });
};
//This can be refactored to store image in DB instead of locally in folder
exports.uploadUserImage = function(req, res) {

};
<commit_msg>Updated local postgres<commit_after>'use strict';
var pg = require('pg');
var conString = 'postgres://' + process.env.POSTGRES + '/postgres';

/**
 * Get tree data for a single tree (profile view)
 */
exports.getTreeData = function(req, res) {
  var treeid = req.params.treeId;
  pg.connect(conString, function(err, client, done) {
    var selectMessages = 'SELECT tree.name, tree.treeid, q.qspecies, tree.plotsize, tree.qcaretaker, tree.plantdate, l.latitude, l.longitude, image.imageurl, image.imagewidth, image.imageheight, image.imagetype from qspecies q JOIN tree ON (q.qspeciesid = tree.qspeciesid) JOIN "location" l ON (l.locationid = tree.locationid) JOIN image ON (q.qspeciesid = image.qspeciesid) WHERE treeid = $1;';
    client.query(selectMessages, [treeid], function(error, results) {
      done();
      if (error) {
        console.log(error, 'THERE WAS AN ERROR');
      }else{
        res.json(results.rows[0]);
      }
    });
  });
};

/**
 * Get tree data for 250 trees (list view)
 */
exports.getAll = function(req, res) {
  pg.connect(conString, function(err, client, done) {
    console.log(err);
    var selectTrees = 'SELECT tree.name, tree.treeid, q.qspecies, l.latitude, l.longitude, thumbnail.url, thumbnail.width, thumbnail.height, thumbnail.contenttype FROM qspecies q JOIN tree ON (q.qspeciesid = tree.qspeciesid) JOIN "location" l ON (l.locationid = tree.locationid) JOIN thumbnail ON (q.qspeciesid = thumbnail.qspeciesid) LIMIT 250;';
    client.query(selectTrees, function(error, results) {
    }, function(error, results) {
      done();
      if(error) {
        console.log('Error is ', error);
      }
      res.json(results.rows);
    });
  });
};

/**
 * Gets tree's messages from database and sends it to client. Request is expecting a treeId.
 * @param req
 * @param res
 */
exports.getMessagesForTree = function(req, res) {
  var treeid = req.params.treeid;
  console.log('in getMessageForTree and treeid is', treeid);
  pg.connect(conString, function(err, client, done) {
    console.log(err);
    var selectMessages = 'SELECT message.message, message.treeid, message.username, message.messageid FROM message  WHERE treeid = $1 LIMIT 100;';
    client.query(selectMessages, [treeid], function(error, results) {
      res.json(results.rows);
    });
    done();
  });
};

/**
 * Gets messages from users and sends it to the database. Request is expecting an username.
 * @param req
 * @param res
 */
exports.getMessagesForUsers = function(req, res) {
  var username = req.params.userid;
  console.log('in getMessageForUsers and username is', username);
  pg.connect(conString, function(err, client, done) {
    console.log(err);
    var selectMessages = 'SELECT message.message, message.treeid, message.username, message.messageid FROM message WHERE username = $1 LIMIT 100;';
    client.query(selectMessages, [username], function(error, results) {
      console.log('results.rows is ', results.rows);
      res.json(results.rows);
    });
    done();
  });
};

/**
 * Gets messages from both users or trees and uploads them to the database. Request expecting treeName or userName,
 * and a treeId.
 * @param req
 * @param res
 */
//
exports.postMessageFromUser = function(req, res) {
  console.log('got into postMessageFromUser');
  var username = req.body.username;
  var message = req.body.message;
  var treeid = req.body.treeid;
  console.log(username, message, treeid);
  pg.connect(conString, function(err, client, done) {
    if(err) { console.log('error is', err); }
    else {
      var insertMessages = 'INSERT INTO message (message, username, treeid) values ($1, $2, $3) RETURNING *;';
      client.query(insertMessages, [message, username, treeid], function (error, results) {
        console.log('postMessageFromUser result is ', results.rows);
        res.json(results.rows);
        done();
      });
    }
  });
};

/**
 * Search tree return 250 results matching the search params. It takes a search param, and an offset. If offset is not
 * provided it will default to 0.
 * @param req
 * @param res
 */
exports.searchTrees = function(req, res) {
  var search = req.search;
  var offset = req.offset || 0;
  var searchString = typeof search === 'string' ? '%' + search + '%' : 'do not use';
  var searchNum = typeof search === 'number' ? search : 0;
  pg.connect(conString, function(err, client, done) {
    console.log('in search trees');
    console.log(err);
    var selectTrees = 'SELECT tree.name, q.qspecies, l.latitude, l.longitude, thumbnail.url, thumbnail.width, ' +
      'thumbnail.height, thumbnail.contenttype FROM qspecies q JOIN tree ON (q.qspeciesid = tree.qspeciesid) JOIN ' +
      '"location" l ON (l.locationid = tree.locationid) JOIN thumbnail ON (q.qspeciesid = thumbnail.qspeciesid) WHERE ' +
      'tree.treeid = $1 OR tree.name LIKE $2 OR q.qspecies = $2 OR q.qspeciesid = $1 LIMIT 250 OFFSET $3;';
    client.query(selectTrees, [searchNum, searchString, offset], function(error, results){
      console.log('error', error);
      //console.log('results', results);
      console.log(results.rows);
      done();
    });
  });
};

/**
 * This function expects a treeid to insert messages. The userid will be saved with the value of -1 which means that
 * the message was sent by the tree.
 * @param req
 * @param res
 */
exports.insertMessagesFromTrees = function(req, res) {
  console.log('got into insertMessagesFromTrees is ');
  var treeid = req.body.treeid;
  var userid = -1;
  var message = req.body.message;
  console.log(treeid, userid, message);
  pg.connect(conString, function(err, client, done) {
    console.log(err);
    var selectMessages = 'INSERT INTO message (message, treeid, userid) values($1, $2, $3)';
    client.query(selectMessages, [message, treeid, userid], function(error, results) {
      console.log('results is ', results);
      res.send(results);
      done();
    });

  });
};
//This can be refactored to store image in DB instead of locally in folder
exports.uploadUserImage = function(req, res) {

};
<|endoftext|>


<commit_before>// Generated on 2014-12-25 using generator-angular 0.10.0
'use strict';

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {

  // Load grunt tasks automatically
  require('load-grunt-tasks')(grunt);

  // Time how long tasks take. Can help when optimizing build times
  require('time-grunt')(grunt);

  // Configurable paths for the application
  var appConfig = {
    app: require('./bower.json').appPath || 'app',
    dist: 'dist'
  };

  // Define the configuration for all the tasks
  grunt.initConfig({

    // Project settings
    yeoman: appConfig,

    // Watches files for changes and runs tasks based on the changed files
    watch: {
      bower: {
        files: ['bower.json'],
        tasks: ['wiredep']
      },
      coffee: {
        files: [
          '<%= yeoman.app %>/scripts/{,*/}*.{coffee,litcoffee,coffee.md}'
        ],
        tasks: ['newer:coffee:dist']
      },
      coffeeTest: {
        files: ['app/{,*}*.spec.coffee'],
        tasks: ['newer:coffee:test', 'karma:unit']
      },
      compass: {
        files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
        tasks: ['compass:server', 'autoprefixer']
      },
      gruntfile: {
        files: ['Gruntfile.js']
      },
      livereload: {
        options: {
          livereload: '<%= connect.options.livereload %>'
        },
        files: [
          '<%= yeoman.app %>/{,*/}*.html',
          '.tmp/styles/{,*/}*.css',
          '.tmp/scripts/{,*/}*.js',
          '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
        ]
      },
      html2js: {
        files: [
          '<%= yeoman.app %>/scripts/{,*/}*.html',
          '<%= yeoman.app %>/views/{,*/}*.html'
        ],
        tasks: ['newer:html2js']
      }
    },

    // The actual grunt server settings
    connect: {
      options: {
        port: 9000,
        // Change this to '0.0.0.0' to access the server from outside.
        hostname: 'localhost',
        livereload: 35729
      },
      livereload: {
        options: {
          open: true,
          middleware: function (connect) {
            return [
              connect.static('.tmp'),
              connect().use(
                '/bower_components',
                connect.static('./bower_components')
              ),
              connect.static(appConfig.app)
            ];
          }
        }
      },
      test: {
        options: {
          port: 9001,
          middleware: function (connect) {
            return [
              connect.static('.tmp'),
              connect.static('test'),
              connect().use(
                '/bower_components',
                connect.static('./bower_components')
              ),
              connect.static(appConfig.app)
            ];
          }
        }
      },
      dist: {
        options: {
          open: true,
          base: '<%= yeoman.dist %>'
        }
      }
    },

    // Make sure code styles are up to par and there are no obvious mistakes
    jshint: {
      options: {
        jshintrc: '.jshintrc',
        reporter: require('jshint-stylish')
      },
      all: {
        src: [
          'Gruntfile.js'
        ]
      }
    },

    // Empties folders to start fresh
    clean: {
      dist: {
        files: [{
          dot: true,
          src: [
            '.tmp',
            '<%= yeoman.dist %>/{,*/}*',
            '!<%= yeoman.dist %>/.git{,*/}*'
          ]
        }]
      },
      server: '.tmp'
    },

    // Add vendor prefixed styles
    autoprefixer: {
      options: {
        browsers: ['last 1 version']
      },
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/styles/',
          src: '{,*/}*.css',
          dest: '.tmp/styles/'
        }]
      }
    },

    // Automatically inject Bower components into the app
    wiredep: {
      app: {
        src: ['<%= yeoman.app %>/index.html'],
        ignorePath:  /\.\.\//
      },
      sass: {
        src: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
        ignorePath: /(\.\.\/){1,2}bower_components\//
      }
    },

    // Compiles CoffeeScript to JavaScript
    coffee: {
      options: {
        sourceMap: true,
        sourceRoot: ''
      },
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/scripts',
          src: '{,*/}*.coffee',
          dest: '.tmp/scripts',
          ext: '.js'
        }]
      },
      test: {
        files: [{
          expand: true,
          cwd: 'test/spec',
          src: '{,*/}*.coffee',
          dest: '.tmp/spec',
          ext: '.js'
        }]
      }
    },

    // Compiles Sass to CSS and generates necessary files if requested
    compass: {
      options: {
        sassDir: '<%= yeoman.app %>/styles',
        cssDir: '.tmp/styles',
        generatedImagesDir: '.tmp/images/generated',
        imagesDir: '<%= yeoman.app %>/images',
        javascriptsDir: '<%= yeoman.app %>/scripts',
        fontsDir: '<%= yeoman.app %>/styles/fonts',
        importPath: './bower_components',
        httpImagesPath: '/images',
        httpGeneratedImagesPath: '/images/generated',
        httpFontsPath: '/styles/fonts',
        relativeAssets: false,
        assetCacheBuster: false,
        raw: 'Sass::Script::Number.precision = 10\n'
      },
      dist: {
        options: {
          generatedImagesDir: '<%= yeoman.dist %>/images/generated'
        }
      },
      server: {
        options: {
          debugInfo: true
        }
      }
    },

    // Renames files for browser caching purposes
    filerev: {
      dist: {
        src: [
          '<%= yeoman.dist %>/scripts/{,*/}*.js',
          '<%= yeoman.dist %>/styles/{,*/}*.css',
          '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
          '<%= yeoman.dist %>/styles/fonts/*'
        ]
      }
    },

    // Reads HTML for usemin blocks to enable smart builds that automatically
    // concat, minify and revision files. Creates configurations in memory so
    // additional tasks can operate on them
    useminPrepare: {
      html: '<%= yeoman.app %>/index.html',
      options: {
        dest: '<%= yeoman.dist %>',
        flow: {
          html: {
            steps: {
              js: ['concat', 'uglifyjs'],
              css: ['cssmin']
            },
            post: {}
          }
        }
      }
    },

    // Performs rewrites based on filerev and the useminPrepare configuration
    usemin: {
      html: ['<%= yeoman.dist %>/{,*/}*.html'],
      css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
      options: {
        assetsDirs: ['<%= yeoman.dist %>','<%= yeoman.dist %>/images']
      }
    },

    // The following *-min tasks will produce minified files in the dist folder
    // By default, your `index.html`'s <!-- Usemin block --> will take care of
    // minification. These next options are pre-configured if you do not wish
    // to use the Usemin blocks.
    // cssmin: {
    //   dist: {
    //     files: {
    //       '<%= yeoman.dist %>/styles/main.css': [
    //         '.tmp/styles/{,*/}*.css'
    //       ]
    //     }
    //   }
    // },
    // uglify: {
    //   dist: {
    //     files: {
    //       '<%= yeoman.dist %>/scripts/scripts.js': [
    //         '<%= yeoman.dist %>/scripts/scripts.js'
    //       ]
    //     }
    //   }
    // },
    // concat: {
    //   dist: {}
    // },

    imagemin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.{png,jpg,jpeg,gif}',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    svgmin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.svg',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    htmlmin: {
      dist: {
        options: {
          collapseWhitespace: true,
          conservativeCollapse: true,
          collapseBooleanAttributes: true,
          removeCommentsFromCDATA: true,
          removeOptionalTags: true
        },
        files: [{
          expand: true,
          cwd: '<%= yeoman.dist %>',
          src: ['*.html', 'views/{,*/}*.html'],
          dest: '<%= yeoman.dist %>'
        }]
      }
    },

    // ng-annotate tries to make the code safe for minification automatically
    // by using the Angular long form for dependency injection.
    ngAnnotate: {
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/concat/scripts',
          src: ['*.js', '!oldieshim.js'],
          dest: '.tmp/concat/scripts'
        }]
      }
    },

    html2js: {
      options: {
        module: 'ec2pricesApp-templates',
        singleModule: true,
        base: '<%= yeoman.app %>',
      },
      main: {
        src: [
          '<%= yeoman.app %>/scripts/{,*/}*.html',
          '<%= yeoman.app %>/views/{,*/}*.html',
        ],
        dest: '.tmp/scripts/templates.js'
      }
    },

    // Replace Google CDN references
    cdnify: {
      dist: {
        html: ['<%= yeoman.dist %>/*.html']
      }
    },

    // Copies remaining files to places other tasks can use
    copy: {
      dist: {
        files: [{
          expand: true,
          dot: true,
          cwd: '<%= yeoman.app %>',
          dest: '<%= yeoman.dist %>',
          src: [
            '*.{ico,png,txt}',
            '.htaccess',
            '*.html',
            'images/{,*/}*.{webp}',
            'fonts/{,*/}*.*',
            'instances.json'
          ]
        }, {
          expand: true,
          cwd: '.tmp/images',
          dest: '<%= yeoman.dist %>/images',
          src: ['generated/*']
        }, {
          expand: true,
          cwd: '.',
          src: 'bower_components/bootstrap-sass-official/assets/fonts/bootstrap/*',
          dest: '<%= yeoman.dist %>'
        }]
      },
      styles: {
        expand: true,
        cwd: '<%= yeoman.app %>/styles',
        dest: '.tmp/styles/',
        src: '{,*/}*.css'
      }
    },

    // Run some tasks in parallel to speed up the build process
    concurrent: {
      server: [
        'coffee:dist',
        'compass:server',
        'html2js'
      ],
      test: [
        'coffee',
        'compass'
      ],
      dist: [
        'coffee',
        'compass:dist',
        'imagemin',
        'svgmin',
        'html2js'
      ]
    },

    // Test settings
    karma: {
      options: {
        configFile: 'test/karma.conf.coffee'
      },
      unit: {
        singleRun: true
      },
      dev: {
        autoWatch: true,
        singleRun: false
      }
    },

    /* jshint ignore:start */
    aws_s3: {
      options: {
        uploadConcurrency: 5,
        sslEnabled: true
      },
      production: {
        options: {
          bucket: process.env.BUCKET,
          differential: true
        },
        files: [{
          expand: true,
          cwd: 'dist',
          src: ['**'],
          dest: '/'
        }]
      }
    },
    /* jshint ignore:end */

    cloudfront: {
      options: {
        distributionId: process.env.DISTRIBUTION_ID,
        listInvalidations: true,
        region: 'us-east-1',
        credentials: {}
      },
      production: {
        CallerReference: Date.now().toString(),
        Paths: {
          Quantity: 1,
          Items: [ '/index.html' ]
        }
      }
    }
  });

  grunt.registerTask('serve', 'Compile then start a connect web server', function (target) {
    if (target === 'dist') {
      return grunt.task.run(['build', 'connect:dist:keepalive']);
    }

    grunt.task.run([
      'clean:server',
      'wiredep',
      'concurrent:server',
      'autoprefixer',
      'connect:livereload',
      'watch'
    ]);
  });

  grunt.registerTask('server', 'DEPRECATED TASK. Use the "serve" task instead', function (target) {
    grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
    grunt.task.run(['serve:' + target]);
  });

  grunt.registerTask('test', [
    'clean:server',
    'concurrent:test',
    'autoprefixer',
    'connect:test',
    'karma:unit'
  ]);

  grunt.registerTask('build', [
    'clean:dist',
    'wiredep',
    'useminPrepare',
    'concurrent:dist',
    'autoprefixer',
    'concat',
    'ngAnnotate',
    'copy:dist',
    'cdnify',
    'cssmin',
    'uglify',
    'filerev',
    'usemin',
    'htmlmin'
  ]);

  grunt.registerTask('default', [
    'newer:jshint',
    'test',
    'build'
  ]);

  grunt.registerTask('deploy', [
    'default',
    'aws_s3:production',
    'cloudfront:production'
  ]);
};
<commit_msg>[grunt:deploy] Invalidate instances.json<commit_after>// Generated on 2014-12-25 using generator-angular 0.10.0
'use strict';

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {

  // Load grunt tasks automatically
  require('load-grunt-tasks')(grunt);

  // Time how long tasks take. Can help when optimizing build times
  require('time-grunt')(grunt);

  // Configurable paths for the application
  var appConfig = {
    app: require('./bower.json').appPath || 'app',
    dist: 'dist'
  };

  // Define the configuration for all the tasks
  grunt.initConfig({

    // Project settings
    yeoman: appConfig,

    // Watches files for changes and runs tasks based on the changed files
    watch: {
      bower: {
        files: ['bower.json'],
        tasks: ['wiredep']
      },
      coffee: {
        files: [
          '<%= yeoman.app %>/scripts/{,*/}*.{coffee,litcoffee,coffee.md}'
        ],
        tasks: ['newer:coffee:dist']
      },
      coffeeTest: {
        files: ['app/{,*}*.spec.coffee'],
        tasks: ['newer:coffee:test', 'karma:unit']
      },
      compass: {
        files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
        tasks: ['compass:server', 'autoprefixer']
      },
      gruntfile: {
        files: ['Gruntfile.js']
      },
      livereload: {
        options: {
          livereload: '<%= connect.options.livereload %>'
        },
        files: [
          '<%= yeoman.app %>/{,*/}*.html',
          '.tmp/styles/{,*/}*.css',
          '.tmp/scripts/{,*/}*.js',
          '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
        ]
      },
      html2js: {
        files: [
          '<%= yeoman.app %>/scripts/{,*/}*.html',
          '<%= yeoman.app %>/views/{,*/}*.html'
        ],
        tasks: ['newer:html2js']
      }
    },

    // The actual grunt server settings
    connect: {
      options: {
        port: 9000,
        // Change this to '0.0.0.0' to access the server from outside.
        hostname: 'localhost',
        livereload: 35729
      },
      livereload: {
        options: {
          open: true,
          middleware: function (connect) {
            return [
              connect.static('.tmp'),
              connect().use(
                '/bower_components',
                connect.static('./bower_components')
              ),
              connect.static(appConfig.app)
            ];
          }
        }
      },
      test: {
        options: {
          port: 9001,
          middleware: function (connect) {
            return [
              connect.static('.tmp'),
              connect.static('test'),
              connect().use(
                '/bower_components',
                connect.static('./bower_components')
              ),
              connect.static(appConfig.app)
            ];
          }
        }
      },
      dist: {
        options: {
          open: true,
          base: '<%= yeoman.dist %>'
        }
      }
    },

    // Make sure code styles are up to par and there are no obvious mistakes
    jshint: {
      options: {
        jshintrc: '.jshintrc',
        reporter: require('jshint-stylish')
      },
      all: {
        src: [
          'Gruntfile.js'
        ]
      }
    },

    // Empties folders to start fresh
    clean: {
      dist: {
        files: [{
          dot: true,
          src: [
            '.tmp',
            '<%= yeoman.dist %>/{,*/}*',
            '!<%= yeoman.dist %>/.git{,*/}*'
          ]
        }]
      },
      server: '.tmp'
    },

    // Add vendor prefixed styles
    autoprefixer: {
      options: {
        browsers: ['last 1 version']
      },
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/styles/',
          src: '{,*/}*.css',
          dest: '.tmp/styles/'
        }]
      }
    },

    // Automatically inject Bower components into the app
    wiredep: {
      app: {
        src: ['<%= yeoman.app %>/index.html'],
        ignorePath:  /\.\.\//
      },
      sass: {
        src: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
        ignorePath: /(\.\.\/){1,2}bower_components\//
      }
    },

    // Compiles CoffeeScript to JavaScript
    coffee: {
      options: {
        sourceMap: true,
        sourceRoot: ''
      },
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/scripts',
          src: '{,*/}*.coffee',
          dest: '.tmp/scripts',
          ext: '.js'
        }]
      },
      test: {
        files: [{
          expand: true,
          cwd: 'test/spec',
          src: '{,*/}*.coffee',
          dest: '.tmp/spec',
          ext: '.js'
        }]
      }
    },

    // Compiles Sass to CSS and generates necessary files if requested
    compass: {
      options: {
        sassDir: '<%= yeoman.app %>/styles',
        cssDir: '.tmp/styles',
        generatedImagesDir: '.tmp/images/generated',
        imagesDir: '<%= yeoman.app %>/images',
        javascriptsDir: '<%= yeoman.app %>/scripts',
        fontsDir: '<%= yeoman.app %>/styles/fonts',
        importPath: './bower_components',
        httpImagesPath: '/images',
        httpGeneratedImagesPath: '/images/generated',
        httpFontsPath: '/styles/fonts',
        relativeAssets: false,
        assetCacheBuster: false,
        raw: 'Sass::Script::Number.precision = 10\n'
      },
      dist: {
        options: {
          generatedImagesDir: '<%= yeoman.dist %>/images/generated'
        }
      },
      server: {
        options: {
          debugInfo: true
        }
      }
    },

    // Renames files for browser caching purposes
    filerev: {
      dist: {
        src: [
          '<%= yeoman.dist %>/scripts/{,*/}*.js',
          '<%= yeoman.dist %>/styles/{,*/}*.css',
          '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
          '<%= yeoman.dist %>/styles/fonts/*'
        ]
      }
    },

    // Reads HTML for usemin blocks to enable smart builds that automatically
    // concat, minify and revision files. Creates configurations in memory so
    // additional tasks can operate on them
    useminPrepare: {
      html: '<%= yeoman.app %>/index.html',
      options: {
        dest: '<%= yeoman.dist %>',
        flow: {
          html: {
            steps: {
              js: ['concat', 'uglifyjs'],
              css: ['cssmin']
            },
            post: {}
          }
        }
      }
    },

    // Performs rewrites based on filerev and the useminPrepare configuration
    usemin: {
      html: ['<%= yeoman.dist %>/{,*/}*.html'],
      css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
      options: {
        assetsDirs: ['<%= yeoman.dist %>','<%= yeoman.dist %>/images']
      }
    },

    // The following *-min tasks will produce minified files in the dist folder
    // By default, your `index.html`'s <!-- Usemin block --> will take care of
    // minification. These next options are pre-configured if you do not wish
    // to use the Usemin blocks.
    // cssmin: {
    //   dist: {
    //     files: {
    //       '<%= yeoman.dist %>/styles/main.css': [
    //         '.tmp/styles/{,*/}*.css'
    //       ]
    //     }
    //   }
    // },
    // uglify: {
    //   dist: {
    //     files: {
    //       '<%= yeoman.dist %>/scripts/scripts.js': [
    //         '<%= yeoman.dist %>/scripts/scripts.js'
    //       ]
    //     }
    //   }
    // },
    // concat: {
    //   dist: {}
    // },

    imagemin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.{png,jpg,jpeg,gif}',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    svgmin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.svg',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    htmlmin: {
      dist: {
        options: {
          collapseWhitespace: true,
          conservativeCollapse: true,
          collapseBooleanAttributes: true,
          removeCommentsFromCDATA: true,
          removeOptionalTags: true
        },
        files: [{
          expand: true,
          cwd: '<%= yeoman.dist %>',
          src: ['*.html', 'views/{,*/}*.html'],
          dest: '<%= yeoman.dist %>'
        }]
      }
    },

    // ng-annotate tries to make the code safe for minification automatically
    // by using the Angular long form for dependency injection.
    ngAnnotate: {
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/concat/scripts',
          src: ['*.js', '!oldieshim.js'],
          dest: '.tmp/concat/scripts'
        }]
      }
    },

    html2js: {
      options: {
        module: 'ec2pricesApp-templates',
        singleModule: true,
        base: '<%= yeoman.app %>',
      },
      main: {
        src: [
          '<%= yeoman.app %>/scripts/{,*/}*.html',
          '<%= yeoman.app %>/views/{,*/}*.html',
        ],
        dest: '.tmp/scripts/templates.js'
      }
    },

    // Replace Google CDN references
    cdnify: {
      dist: {
        html: ['<%= yeoman.dist %>/*.html']
      }
    },

    // Copies remaining files to places other tasks can use
    copy: {
      dist: {
        files: [{
          expand: true,
          dot: true,
          cwd: '<%= yeoman.app %>',
          dest: '<%= yeoman.dist %>',
          src: [
            '*.{ico,png,txt}',
            '.htaccess',
            '*.html',
            'images/{,*/}*.{webp}',
            'fonts/{,*/}*.*',
            'instances.json'
          ]
        }, {
          expand: true,
          cwd: '.tmp/images',
          dest: '<%= yeoman.dist %>/images',
          src: ['generated/*']
        }, {
          expand: true,
          cwd: '.',
          src: 'bower_components/bootstrap-sass-official/assets/fonts/bootstrap/*',
          dest: '<%= yeoman.dist %>'
        }]
      },
      styles: {
        expand: true,
        cwd: '<%= yeoman.app %>/styles',
        dest: '.tmp/styles/',
        src: '{,*/}*.css'
      }
    },

    // Run some tasks in parallel to speed up the build process
    concurrent: {
      server: [
        'coffee:dist',
        'compass:server',
        'html2js'
      ],
      test: [
        'coffee',
        'compass'
      ],
      dist: [
        'coffee',
        'compass:dist',
        'imagemin',
        'svgmin',
        'html2js'
      ]
    },

    // Test settings
    karma: {
      options: {
        configFile: 'test/karma.conf.coffee'
      },
      unit: {
        singleRun: true
      },
      dev: {
        autoWatch: true,
        singleRun: false
      }
    },

    /* jshint ignore:start */
    aws_s3: {
      options: {
        uploadConcurrency: 5,
        sslEnabled: true
      },
      production: {
        options: {
          bucket: process.env.BUCKET,
          differential: true
        },
        files: [{
          expand: true,
          cwd: 'dist',
          src: ['**'],
          dest: '/'
        }]
      }
    },
    /* jshint ignore:end */

    cloudfront: {
      options: {
        distributionId: process.env.DISTRIBUTION_ID,
        listInvalidations: true,
        region: 'us-east-1',
        credentials: {}
      },
      production: {
        CallerReference: Date.now().toString(),
        Paths: {
          Quantity: 1,
          Items: [ '/index.html', '/instances.json' ]
        }
      }
    }
  });

  grunt.registerTask('serve', 'Compile then start a connect web server', function (target) {
    if (target === 'dist') {
      return grunt.task.run(['build', 'connect:dist:keepalive']);
    }

    grunt.task.run([
      'clean:server',
      'wiredep',
      'concurrent:server',
      'autoprefixer',
      'connect:livereload',
      'watch'
    ]);
  });

  grunt.registerTask('server', 'DEPRECATED TASK. Use the "serve" task instead', function (target) {
    grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
    grunt.task.run(['serve:' + target]);
  });

  grunt.registerTask('test', [
    'clean:server',
    'concurrent:test',
    'autoprefixer',
    'connect:test',
    'karma:unit'
  ]);

  grunt.registerTask('build', [
    'clean:dist',
    'wiredep',
    'useminPrepare',
    'concurrent:dist',
    'autoprefixer',
    'concat',
    'ngAnnotate',
    'copy:dist',
    'cdnify',
    'cssmin',
    'uglify',
    'filerev',
    'usemin',
    'htmlmin'
  ]);

  grunt.registerTask('default', [
    'newer:jshint',
    'test',
    'build'
  ]);

  grunt.registerTask('deploy', [
    'default',
    'aws_s3:production',
    'cloudfront:production'
  ]);
};
<|endoftext|>


<commit_before>module.exports = function(grunt) {
    'use strict';

    grunt.initConfig({

        // Load meta info from package.json
        pkg: grunt.file.readJSON('package.json'),

        // Banner for distribution CSS and JS
        banner: '/*!\n' +
            ' * Synergic UI\n' +
            ' * Built on the shoulders of a giant: Bootstrap 3\n' +
            ' * <%= pkg.homepage %>\n' +
            ' *\n' +
            ' * Created by <%= pkg.author %> (www.synergic.cz)\n' +
            ' * HTML & LESS © 2014 Adam Kudrna\n' +
            ' * JavaScript © 2014 Martin Bohal\n' +
            ' *\n' +
            ' * v<%= pkg.version %> (<%= grunt.template.today("d mmmm yyyy") %>)\n' +
            ' */\n',

        // Clean
        clean: {
            temp: '.tmp',
            dist: 'dist',
            styleguide: 'styleguide'
        },

        // Compile LESS
        less: {
            dist: {
                files: {
                    'dist/css/<%= pkg.name %>.css': 'src/less/dist.less'
                },
                options: {
                    banner: '<%= banner %>',
                    strictUnits: true
                }
            }
        },

        // Add vendor prefixes to CSS, based on Bootstrap settings
        autoprefixer: {
            options: {
                browsers: [
                    'Android 2.3',
                    'Android >= 4',
                    'Chrome >= 20',
                    'Firefox >= 24', // Firefox 24 is the latest ESR
                    'Explorer >= 8',
                    'iOS >= 6',
                    'Opera >= 12',
                    'Safari >= 6'
                ]
            },
            core: {
                options: {
                    map: true
                },
                src: 'dist/css/<%= pkg.name %>.css'
            },
            styleguide: {
                src: 'styleguide/public/kss.css'
            }
        },

        // Minify CSS
        cssmin: {
            dist: {
                files: {
                    'dist/css/<%= pkg.name %>.min.css': 'dist/css/<%= pkg.name %>.css'
                }
            }
        },

        // Lint custom JS
        jshint: {
            grunt: 'Gruntfile.js',
            core: 'src/js/*.js',
            tests: 'src/js/tests/unit/*.js'
        },

        // Concat all JS
        concat: {
            dist: {
                src: [
                    'src/js/*.js'
                ],
                dest: 'dist/js/<%= pkg.name %>.js'
            },
            options: {
                banner: '<%= banner %>',
                sourceMap: true
            }
        },

        // Minify concatenated JS
        uglify: {
            options: {
                preserveComments: 'some'
            },
            dist: {
                files: {
                    'dist/js/<%= pkg.name %>.min.js': 'dist/js/<%= pkg.name %>.js'
                }
            }
        },

        // Replace versions
        replace: {
            styleguide: {
                src: 'src/styleguide/index.html',
                dest: '.tmp/styleguide/index.html',
                replacements: [
                    {
                        from: '%VERSION%',
                        to: '<%= pkg.version %>'
                    },
                    {
                        from: '%YEAR%',
                        to: '<%= grunt.template.today("yyyy") %>'
                    }
                ]
            }
        },

        // Generate styleguide
        styleguide: {
            options: {
                template: {
                    src: '.tmp/styleguide'
                },
                framework: {
                    name: 'kss'
                }
            },
            all: {
                files: [{
                    'styleguide': 'src/less/dist.less'
                }]
            }
        },

        // Copy files
        copy: {
            fonts: {
                files: [{
                    expand: true,
                    cwd: 'bower_components/bootstrap/dist/',
                    src: ['fonts/*'],
                    dest: 'dist/'
                }]
            },
            styleguideSrc: {
                files: [{
                    expand: true,
                    cwd: 'src/styleguide/public/',
                    src: ['**/*'],
                    dest: '.tmp/styleguide/public/'
                }]
            },
            styleguide: {
                files: [
                    {
                        expand: true,
                        flatten: true,
                        src: [
                            'bower_components/jquery/dist/jquery.min.*',
                            'bower_components/eonasdan-bootstrap-datetimepicker/build/js/*',
                            'bower_components/moment/min/moment.min.js',
                            'bower_components/select2/select2.min.js'
                        ],
                        dest: 'styleguide/public/js/'
                    },
                    {
                        expand: true,
                        cwd: 'bower_components/bootstrap/dist/',
                        src: ['fonts/*', 'js/*'],
                        dest: 'styleguide/public/'
                    },
                    {
                        expand: true,
                        cwd: 'dist/',
                        src: ['css/*', 'js/*'],
                        dest: 'styleguide/public/'
                    },
                    {
                        expand: true,
                        cwd: 'src/images/',
                        src: ['*'],
                        dest: 'styleguide/public/images/'
                    }
                ]
            }
        },

        // Watch task
        watch: {
            less: {
                files: ['src/less/**/*.less'],
                tasks: [
                    'build-css',
                    'build-styleguide'
                ]
            },
            js: {
                files: ['src/js/*.js'],
                tasks: [
                    'build-js',
                    'build-styleguide'
                ]
            },
            styleguide: {
                files: [
                    'src/less/styleguide.md',
                    'src/styleguide/**/*'
                ],
                tasks: [
                    'build-styleguide'
                ]
            }
        },

        // Browser Sync
        browserSync: {
            dev: {
                bsFiles: {
                    src : [
                        'styleguide/public/css/*.css',
                        'styleguide/public/js/*.js',
                        'styleguide/*.html'
                    ]
                },
                options: {
                    server: {
                        baseDir: './styleguide/'
                    },
                    watchTask: true
                }
            },
            test: {
                bsFiles: {
                    src : [
                        'src/js/**/*.js'
                    ]
                },
                options: {
                    server: {
                        baseDir: './'
                    }
                }
            }
        },

        // JS tests
        qunit: {
            options: {
                noGlobals: true
            },
            all: ['src/js/tests/index.html']
        }
    });

    // Autoload all Grunt tasks
    require('load-grunt-tasks')(grunt);

    // Measure task execution times
    require('time-grunt')(grunt);

    // Build tasks
    grunt.registerTask('build-css', [
        'less',
        'autoprefixer:core',
        'cssmin'
    ]);

    grunt.registerTask('build-js', [
        'jshint:core',
        'concat',
        'uglify'
    ]);

    grunt.registerTask('build-styleguide', [
        'replace:styleguide',
        'copy:styleguideSrc',
        'styleguide',
        'autoprefixer:styleguide',
        'copy:styleguide'
    ]);

    grunt.registerTask('build', [
        'clean',
        'build-css',
        'build-js',
        'build-styleguide',
        'copy:fonts'
    ]);

    // Serve and watch
    grunt.registerTask('serve', [
        'build',
        'browserSync:dev',
        'watch'
    ]);

    // Run JS tests in browser
    // NOTE: add `src/js/tests/` to URL (typically localhost:3000) to view the test page.
    grunt.registerTask('test-remote', [
        'jshint',
        'browserSync:test'
    ]);

    // Run tests
    grunt.registerTask('test', [
        'jshint',
        'qunit'
    ]);

    // Default task
    grunt.registerTask('default', 'test');
};
<commit_msg>Fixed LESS source map<commit_after>module.exports = function(grunt) {
    'use strict';

    grunt.initConfig({

        // Load meta info from package.json
        pkg: grunt.file.readJSON('package.json'),

        // Banner for distribution CSS and JS
        banner: '/*!\n' +
            ' * Synergic UI\n' +
            ' * Built on the shoulders of a giant: Bootstrap 3\n' +
            ' * <%= pkg.homepage %>\n' +
            ' *\n' +
            ' * Created by <%= pkg.author %> (www.synergic.cz)\n' +
            ' * HTML & LESS © 2014 Adam Kudrna\n' +
            ' * JavaScript © 2014 Martin Bohal\n' +
            ' *\n' +
            ' * v<%= pkg.version %> (<%= grunt.template.today("d mmmm yyyy") %>)\n' +
            ' */\n',

        // Clean
        clean: {
            temp: '.tmp',
            dist: 'dist',
            styleguide: 'styleguide'
        },

        // Compile LESS
        less: {
            dist: {
                files: {
                    'dist/css/<%= pkg.name %>.css': 'src/less/dist.less'
                },
                options: {
                    banner: '<%= banner %>',
                    strictUnits: true,
                    sourceMap: true,
                    sourceMapFilename: 'dist/css/<%= pkg.name %>.css.map',
                    sourceMapURL: '<%= pkg.name %>.css.map',
                    sourceMapRootpath: '../../../'
                }
            }
        },

        // Add vendor prefixes to CSS, based on Bootstrap settings
        autoprefixer: {
            options: {
                browsers: [
                    'Android 2.3',
                    'Android >= 4',
                    'Chrome >= 20',
                    'Firefox >= 24', // Firefox 24 is the latest ESR
                    'Explorer >= 8',
                    'iOS >= 6',
                    'Opera >= 12',
                    'Safari >= 6'
                ]
            },
            core: {
                options: {
                    map: true
                },
                src: 'dist/css/<%= pkg.name %>.css'
            },
            styleguide: {
                src: 'styleguide/public/kss.css'
            }
        },

        // Minify CSS
        cssmin: {
            dist: {
                files: {
                    'dist/css/<%= pkg.name %>.min.css': 'dist/css/<%= pkg.name %>.css'
                }
            }
        },

        // Lint custom JS
        jshint: {
            grunt: 'Gruntfile.js',
            core: 'src/js/*.js',
            tests: 'src/js/tests/unit/*.js'
        },

        // Concat all JS
        concat: {
            dist: {
                src: [
                    'src/js/*.js'
                ],
                dest: 'dist/js/<%= pkg.name %>.js'
            },
            options: {
                banner: '<%= banner %>',
                sourceMap: true
            }
        },

        // Minify concatenated JS
        uglify: {
            options: {
                preserveComments: 'some'
            },
            dist: {
                files: {
                    'dist/js/<%= pkg.name %>.min.js': 'dist/js/<%= pkg.name %>.js'
                }
            }
        },

        // Replace versions
        replace: {
            styleguide: {
                src: 'src/styleguide/index.html',
                dest: '.tmp/styleguide/index.html',
                replacements: [
                    {
                        from: '%VERSION%',
                        to: '<%= pkg.version %>'
                    },
                    {
                        from: '%YEAR%',
                        to: '<%= grunt.template.today("yyyy") %>'
                    }
                ]
            }
        },

        // Generate styleguide
        styleguide: {
            options: {
                template: {
                    src: '.tmp/styleguide'
                },
                framework: {
                    name: 'kss'
                }
            },
            all: {
                files: [{
                    'styleguide': 'src/less/dist.less'
                }]
            }
        },

        // Copy files
        copy: {
            fonts: {
                files: [{
                    expand: true,
                    cwd: 'bower_components/bootstrap/dist/',
                    src: ['fonts/*'],
                    dest: 'dist/'
                }]
            },
            styleguideSrc: {
                files: [{
                    expand: true,
                    cwd: 'src/styleguide/public/',
                    src: ['**/*'],
                    dest: '.tmp/styleguide/public/'
                }]
            },
            styleguide: {
                files: [
                    {
                        expand: true,
                        flatten: true,
                        src: [
                            'bower_components/jquery/dist/jquery.min.*',
                            'bower_components/eonasdan-bootstrap-datetimepicker/build/js/*',
                            'bower_components/moment/min/moment.min.js',
                            'bower_components/select2/select2.min.js'
                        ],
                        dest: 'styleguide/public/js/'
                    },
                    {
                        expand: true,
                        cwd: 'bower_components/bootstrap/dist/',
                        src: ['fonts/*', 'js/*'],
                        dest: 'styleguide/public/'
                    },
                    {
                        expand: true,
                        cwd: 'dist/',
                        src: ['css/*', 'js/*'],
                        dest: 'styleguide/public/'
                    },
                    {
                        expand: true,
                        cwd: 'src/images/',
                        src: ['*'],
                        dest: 'styleguide/public/images/'
                    }
                ]
            }
        },

        // Watch task
        watch: {
            less: {
                files: ['src/less/**/*.less'],
                tasks: [
                    'build-css',
                    'build-styleguide'
                ]
            },
            js: {
                files: ['src/js/*.js'],
                tasks: [
                    'build-js',
                    'build-styleguide'
                ]
            },
            styleguide: {
                files: [
                    'src/less/styleguide.md',
                    'src/styleguide/**/*'
                ],
                tasks: [
                    'build-styleguide'
                ]
            }
        },

        // Browser Sync
        browserSync: {
            dev: {
                bsFiles: {
                    src : [
                        'styleguide/public/css/*.css',
                        'styleguide/public/js/*.js',
                        'styleguide/*.html'
                    ]
                },
                options: {
                    server: {
                        baseDir: './styleguide/'
                    },
                    watchTask: true
                }
            },
            test: {
                bsFiles: {
                    src : [
                        'src/js/**/*.js'
                    ]
                },
                options: {
                    server: {
                        baseDir: './'
                    }
                }
            }
        },

        // JS tests
        qunit: {
            options: {
                noGlobals: true
            },
            all: ['src/js/tests/index.html']
        }
    });

    // Autoload all Grunt tasks
    require('load-grunt-tasks')(grunt);

    // Measure task execution times
    require('time-grunt')(grunt);

    // Build tasks
    grunt.registerTask('build-css', [
        'less',
        'autoprefixer:core',
        'cssmin'
    ]);

    grunt.registerTask('build-js', [
        'jshint:core',
        'concat',
        'uglify'
    ]);

    grunt.registerTask('build-styleguide', [
        'replace:styleguide',
        'copy:styleguideSrc',
        'styleguide',
        'autoprefixer:styleguide',
        'copy:styleguide'
    ]);

    grunt.registerTask('build', [
        'clean',
        'build-css',
        'build-js',
        'build-styleguide',
        'copy:fonts'
    ]);

    // Serve and watch
    grunt.registerTask('serve', [
        'build',
        'browserSync:dev',
        'watch'
    ]);

    // Run JS tests in browser
    // NOTE: add `src/js/tests/` to URL (typically localhost:3000) to view the test page.
    grunt.registerTask('test-remote', [
        'jshint',
        'browserSync:test'
    ]);

    // Run tests
    grunt.registerTask('test', [
        'jshint',
        'qunit'
    ]);

    // Default task
    grunt.registerTask('default', 'test');
};
<|endoftext|>


<commit_before>import React, { PropTypes, Component } from 'react';
import { DragSource, DropTarget } from 'react-dnd';
import DragDropItemTypes from './DragDropItemTypes';
import Well from './Well';

const wellSource = {
  beginDrag(props) {
    return props;
  }
};

const wellTarget = {
  canDrop(props, monitor){
    const item = monitor.getItem();
    const itemType = monitor.getItemType();
    let canDrop = true;
    if (itemType == 'sample' && props.well.sample) {
      canDrop = false;
    } else if (itemType == 'well' && ! item.well.sample && ! props.well.sample) {
      canDrop = false;
    }
    return canDrop;
  },
  drop(props, monitor){
    const item = monitor.getItem();
    const itemType = monitor.getItemType();
    if (itemType == 'sample') {
      props.dropSample(item.element, props.well);
    } else if (itemType == 'well') {
      props.swapWells(item.well, props.well);
    }
  }
};

const collectTarget = (connect, monitor) => ({
  connectDropTarget: connect.dropTarget(),
  isOver: monitor.isOver(),
  canDrop: monitor.canDrop()
});

const collectSource = (connect, monitor) => ({
  connectDragSource: connect.dragSource(),
  isDragging: monitor.isDragging()
});

class WellContainer extends Component {
  componentWillReceiveProps(nextProps) {
    const {active, hideOverlay} = this.props;
    if (active && nextProps.isDragging) {
      hideOverlay();
    }
  }

  render() {
    const {style, isDragging, connectDragSource, connectDropTarget, well, isOver, canDrop, active} = this.props;
    const containerStyle = {
      paddingTop: 9,
      borderRadius: '50%',
      float: 'left',
      borderWidth: 4,
      borderStyle: 'solid',
      borderColor: 'lightgray',
      textAlign: 'center',
      verticalAlign: 'middle',
      lineHeight: 2,
      cursor: 'move',
      backgroundColor: 'white'
    };
    if (active) {
      containerStyle.backgroundColor = '#337ab7';
      containerStyle.color = 'white';
    }
    if (isDragging) {
      containerStyle.opacity = 0;
    }
    if (well.sample) {
      containerStyle.borderColor = 'gray';
    }
    if (isOver && canDrop) {
      containerStyle.borderStyle = 'dashed';
      containerStyle.borderColor = '#337ab7';
      containerStyle.opacity = 0;
    } else if (canDrop) {
      containerStyle.borderStyle = 'dashed';
    }

    return (
      connectDragSource(connectDropTarget(

          <Well
            active={active}
            sample={well.sample}
            />

      ))
    );
  }
}

export default DropTarget([DragDropItemTypes.WELL, DragDropItemTypes.SAMPLE], wellTarget, collectTarget)(DragSource(DragDropItemTypes.WELL, wellSource, collectSource)(WellContainer));

WellContainer.propTypes = {
  connectDragSource: PropTypes.func.isRequired,
  connectDropTarget: PropTypes.func.isRequired,
  isDragging: PropTypes.bool.isRequired,
  isOver: PropTypes.bool.isRequired,
  canDrop: PropTypes.bool.isRequired,
  swapWells: PropTypes.func.isRequired,
  dropSample: PropTypes.func.isRequired,
  well: PropTypes.object
};<commit_msg>fix sampleDrop on wellplate<commit_after>import React, { PropTypes, Component } from 'react';
import { DragSource, DropTarget } from 'react-dnd';
import DragDropItemTypes from './DragDropItemTypes';
import Well from './Well';

const wellSource = {
  beginDrag(props) {
    return props;
  }
};

const wellTarget = {
  canDrop(props, monitor){
    const item = monitor.getItem();
    const itemType = monitor.getItemType();
    let canDrop = true;
    if (itemType == 'sample' && props.well.sample) {
      canDrop = false;
    } else if (itemType == 'well' && ! item.well.sample && ! props.well.sample) {
      canDrop = false;
    }
    return canDrop;
  },
  drop(props, monitor){
    const item = monitor.getItem();
    const itemType = monitor.getItemType();
    if (itemType == 'sample') {
      props.dropSample(item.element.unwrap(), props.well);
    } else if (itemType == 'well') {
      props.swapWells(item.well, props.well);
    }
  }
};

const collectTarget = (connect, monitor) => ({
  connectDropTarget: connect.dropTarget(),
  isOver: monitor.isOver(),
  canDrop: monitor.canDrop()
});

const collectSource = (connect, monitor) => ({
  connectDragSource: connect.dragSource(),
  isDragging: monitor.isDragging()
});

class WellContainer extends Component {
  componentWillReceiveProps(nextProps) {
    const {active, hideOverlay} = this.props;
    if (active && nextProps.isDragging) {
      hideOverlay();
    }
  }

  render() {
    const {style, isDragging, connectDragSource, connectDropTarget, well, isOver, canDrop, active} = this.props;
    const containerStyle = {
      paddingTop: 9,
      borderRadius: '50%',
      float: 'left',
      borderWidth: 4,
      borderStyle: 'solid',
      borderColor: 'lightgray',
      textAlign: 'center',
      verticalAlign: 'middle',
      lineHeight: 2,
      cursor: 'move',
      backgroundColor: 'white'
    };
    if (active) {
      containerStyle.backgroundColor = '#337ab7';
      containerStyle.color = 'white';
    }
    if (isDragging) {
      containerStyle.opacity = 0;
    }
    if (well.sample) {
      containerStyle.borderColor = 'gray';
    }
    if (isOver && canDrop) {
      containerStyle.borderStyle = 'dashed';
      containerStyle.borderColor = '#337ab7';
      containerStyle.opacity = 0;
    } else if (canDrop) {
      containerStyle.borderStyle = 'dashed';
    }

    return (
      connectDragSource(connectDropTarget(

          <Well
            active={active}
            sample={well.sample}
            />

      ))
    );
  }
}

export default DropTarget([DragDropItemTypes.WELL, DragDropItemTypes.SAMPLE], wellTarget, collectTarget)(DragSource(DragDropItemTypes.WELL, wellSource, collectSource)(WellContainer));

WellContainer.propTypes = {
  connectDragSource: PropTypes.func.isRequired,
  connectDropTarget: PropTypes.func.isRequired,
  isDragging: PropTypes.bool.isRequired,
  isOver: PropTypes.bool.isRequired,
  canDrop: PropTypes.bool.isRequired,
  swapWells: PropTypes.func.isRequired,
  dropSample: PropTypes.func.isRequired,
  well: PropTypes.object
};
<|endoftext|>


<commit_before>describe("chorus.views.visualizations.BoxPlot", function() {
    var leftX   = chorus.svgHelpers.leftX,
        rightX  = chorus.svgHelpers.rightX,
        width   = chorus.svgHelpers.width,
        height  = chorus.svgHelpers.height,
        centerX = chorus.svgHelpers.centerX,
        topY    = chorus.svgHelpers.topY,
        bottomY = chorus.svgHelpers.bottomY,
        centerY = chorus.svgHelpers.centerY;

    beforeEach(function() {
        this.task = fixtures.boxplotTaskWithResult({
            rows: [
                { bucket: 'aardvark',   min: 1,   firstQuartile: 1,   median: 2.5, thirdQuartile: 3,   max: 4,   percentage: "25%" },
                { bucket: 'beluga',     min: 100, firstQuartile: 100, median: 250, thirdQuartile: 300, max: 400, percentage: "33.3%" },
                { bucket: 'chupacabra', min: 10,  firstQuartile: 10,  median: 25,  thirdQuartile: 30,  max: 40,  percentage: "81.5%" }
            ],
            xAxis: "foo",
            yAxis: "bar"
        });

        this.view = new chorus.views.visualizations.Boxplot({ model: this.task });
    });

    describe("#render", function() {
        beforeEach(function() {
            $("#jasmine_content").append(this.view.el);
            this.view.render();

            this.boxes = this.view.$("g.box");
            this.quartileRectangles = this.boxes.find(".quartile");
            this.medianLines = this.boxes.find("line.median");
            this.minWhiskers = this.boxes.find("line.whisker.min")
            this.maxWhiskers = this.boxes.find("line.whisker.max")
            this.whiskers = this.boxes.find("line.whisker");
            this.midlines = this.boxes.find("line.midline");
        });

        beforeEach(function() {
            this.addMatchers(chorus.svgHelpers.matchers);
        });

        it("has the correct axis labels", function() {
            expect(this.view.$('.xaxis .axis_label').text()).toBe("foo")
            expect(this.view.$('.yaxis .axis_label').text()).toBe("bar")
        })

        describe("the quartile rectangles", function() {
            it("has one for each bucket", function() {
                expect(this.quartileRectangles.length).toBe(3);
            });

            it("draws them in order from left to right", function() {
                expect(this.quartileRectangles).toBeOrderedLeftToRight();
            });

            it("draws them with equal width", function() {
                var standardWidth = width(this.quartileRectangles[0]);
                _.each(this.quartileRectangles, function(rect) {
                    expect(width(rect)).toEqual(standardWidth);
                });
            });

            it("draws them with some padding in between", function() {
                _.each(this.quartileRectangles, function(rect, i) {
                    if (i === 0) return;
                    expect(leftX(rect)).toBeGreaterThan(rightX(this.quartileRectangles[i-1]) + 5);
                }, this);
            });

            it("centers the rectangles on the x axis ticks", function() {
                var xTicks = this.view.$(".xaxis .tick");
                _.each(this.quartileRectangles, function(rect, i) {
                    expect(centerX(rect)).toBeCloseTo(centerX(xTicks[i]));
                }, this);
            });
        });

        describe("the median lines", function() {
            it("draws one for each bucket", function() {
                expect(this.medianLines.length).toBe(3);
            });

            it("draws it inside the rectangle", function() {
                _.each(this.medianLines, function(line, i) {
                    var rect = this.quartileRectangles[i];

                    expect(line).toBeHorizontal();

                    expect(leftX(line)).toBeCloseTo(leftX(rect));
                    expect(rightX(line)).toBeCloseTo(rightX(rect));

                    expect(topY(line)).toBeGreaterThan(topY(rect));
                    expect(bottomY(line)).toBeLessThan(bottomY(rect));
                }, this);
            });
        });

        describe("the whiskers", function() {
            it("draws a minimum and a maximum whisker for each bucket", function() {
                expect(this.maxWhiskers.length).toBe(3);
                expect(this.minWhiskers.length).toBe(3);
                expect(this.whiskers.length).toBe(6);
            });

            it("aligns them horizontally with the boxes", function() {
                _.each(this.minWhiskers, function(whisker, i) {
                    expect(centerX(whisker)).toBeCloseTo(centerX(this.quartileRectangles[i]), 1);
                }, this);

                _.each(this.maxWhiskers, function(whisker, i) {
                    expect(centerX(whisker)).toBeCloseTo(centerX(this.quartileRectangles[i]), 1);
                }, this);
            });

            it("draws them horizontally", function() {
                _.each(this.whiskers, function(whisker, i) {
                    expect(whisker).toBeHorizontal();
                }, this);
            });

            it("draws them narrower than the boxes", function() {
                var boxWidth = width(this.quartileRectangles[0]);
                _.each(this.minWhiskers, function(whisker, i) {
                    expect(width(whisker)).toBeLessThan(boxWidth);
                }, this);
            });
        });

        describe("the midlines", function() {
            it("draws one for each bucket", function() {
                expect(this.midlines.length).toBe(3);
            });

            it("draws them vertically", function() {
                _.each(this.midlines, function(line) {
                    expect(line).toBeVertical();
                });
            });

            it("draws them at the center of the boxes", function() {
                _.each(this.midlines, function(line, i) {
                    expect(centerX(line)).toBeCloseTo(centerX(this.quartileRectangles[i]), 1);
                }, this);
            });

            it("draws them from the minimum whisker up to the maximum whisker", function() {
                _.each(this.midlines, function(line, i) {
                    expect(topY(line)).toEqual(topY(this.maxWhiskers[i]));
                    expect(bottomY(line)).toEqual(bottomY(this.minWhiskers[i]));
                }, this);
            });
        });

        xit("renders no xtick lines by default", function() {
            expect(this.view.$(".axis.south .grid line").length).toBe(0);
        });

        xit("renders ytick lines by default", function() {
            expect(this.view.$(".axis.west .grid line").length).toBeGreaterThan(1);
        });
    })
});

<commit_msg>reduce precision again to fix firefox failure<commit_after>describe("chorus.views.visualizations.BoxPlot", function() {
    var leftX   = chorus.svgHelpers.leftX,
        rightX  = chorus.svgHelpers.rightX,
        width   = chorus.svgHelpers.width,
        height  = chorus.svgHelpers.height,
        centerX = chorus.svgHelpers.centerX,
        topY    = chorus.svgHelpers.topY,
        bottomY = chorus.svgHelpers.bottomY,
        centerY = chorus.svgHelpers.centerY;

    beforeEach(function() {
        this.task = fixtures.boxplotTaskWithResult({
            rows: [
                { bucket: 'aardvark',   min: 1,   firstQuartile: 1,   median: 2.5, thirdQuartile: 3,   max: 4,   percentage: "25%" },
                { bucket: 'beluga',     min: 100, firstQuartile: 100, median: 250, thirdQuartile: 300, max: 400, percentage: "33.3%" },
                { bucket: 'chupacabra', min: 10,  firstQuartile: 10,  median: 25,  thirdQuartile: 30,  max: 40,  percentage: "81.5%" }
            ],
            xAxis: "foo",
            yAxis: "bar"
        });

        this.view = new chorus.views.visualizations.Boxplot({ model: this.task });
    });

    describe("#render", function() {
        beforeEach(function() {
            $("#jasmine_content").append(this.view.el);
            this.view.render();

            this.boxes = this.view.$("g.box");
            this.quartileRectangles = this.boxes.find(".quartile");
            this.medianLines = this.boxes.find("line.median");
            this.minWhiskers = this.boxes.find("line.whisker.min")
            this.maxWhiskers = this.boxes.find("line.whisker.max")
            this.whiskers = this.boxes.find("line.whisker");
            this.midlines = this.boxes.find("line.midline");
        });

        beforeEach(function() {
            this.addMatchers(chorus.svgHelpers.matchers);
        });

        it("has the correct axis labels", function() {
            expect(this.view.$('.xaxis .axis_label').text()).toBe("foo")
            expect(this.view.$('.yaxis .axis_label').text()).toBe("bar")
        })

        describe("the quartile rectangles", function() {
            it("has one for each bucket", function() {
                expect(this.quartileRectangles.length).toBe(3);
            });

            it("draws them in order from left to right", function() {
                expect(this.quartileRectangles).toBeOrderedLeftToRight();
            });

            it("draws them with equal width", function() {
                var standardWidth = width(this.quartileRectangles[0]);
                _.each(this.quartileRectangles, function(rect) {
                    expect(width(rect)).toEqual(standardWidth);
                });
            });

            it("draws them with some padding in between", function() {
                _.each(this.quartileRectangles, function(rect, i) {
                    if (i === 0) return;
                    expect(leftX(rect)).toBeGreaterThan(rightX(this.quartileRectangles[i-1]) + 5);
                }, this);
            });

            it("centers the rectangles on the x axis ticks", function() {
                var xTicks = this.view.$(".xaxis .tick");
                _.each(this.quartileRectangles, function(rect, i) {
                    expect(centerX(rect)).toBeCloseTo(centerX(xTicks[i]), 1);
                }, this);
            });
        });

        describe("the median lines", function() {
            it("draws one for each bucket", function() {
                expect(this.medianLines.length).toBe(3);
            });

            it("draws it inside the rectangle", function() {
                _.each(this.medianLines, function(line, i) {
                    var rect = this.quartileRectangles[i];

                    expect(line).toBeHorizontal();

                    expect(leftX(line)).toBeCloseTo(leftX(rect), 1);
                    expect(rightX(line)).toBeCloseTo(rightX(rect), 1);

                    expect(topY(line)).toBeGreaterThan(topY(rect));
                    expect(bottomY(line)).toBeLessThan(bottomY(rect));
                }, this);
            });
        });

        describe("the whiskers", function() {
            it("draws a minimum and a maximum whisker for each bucket", function() {
                expect(this.maxWhiskers.length).toBe(3);
                expect(this.minWhiskers.length).toBe(3);
                expect(this.whiskers.length).toBe(6);
            });

            it("aligns them horizontally with the boxes", function() {
                _.each(this.minWhiskers, function(whisker, i) {
                    expect(centerX(whisker)).toBeCloseTo(centerX(this.quartileRectangles[i]), 1);
                }, this);

                _.each(this.maxWhiskers, function(whisker, i) {
                    expect(centerX(whisker)).toBeCloseTo(centerX(this.quartileRectangles[i]), 1);
                }, this);
            });

            it("draws them horizontally", function() {
                _.each(this.whiskers, function(whisker, i) {
                    expect(whisker).toBeHorizontal();
                }, this);
            });

            it("draws them narrower than the boxes", function() {
                var boxWidth = width(this.quartileRectangles[0]);
                _.each(this.minWhiskers, function(whisker, i) {
                    expect(width(whisker)).toBeLessThan(boxWidth);
                }, this);
            });
        });

        describe("the midlines", function() {
            it("draws one for each bucket", function() {
                expect(this.midlines.length).toBe(3);
            });

            it("draws them vertically", function() {
                _.each(this.midlines, function(line) {
                    expect(line).toBeVertical();
                });
            });

            it("draws them at the center of the boxes", function() {
                _.each(this.midlines, function(line, i) {
                    expect(centerX(line)).toBeCloseTo(centerX(this.quartileRectangles[i]), 1);
                }, this);
            });

            it("draws them from the minimum whisker up to the maximum whisker", function() {
                _.each(this.midlines, function(line, i) {
                    expect(topY(line)).toEqual(topY(this.maxWhiskers[i]));
                    expect(bottomY(line)).toEqual(bottomY(this.minWhiskers[i]));
                }, this);
            });
        });

        xit("renders no xtick lines by default", function() {
            expect(this.view.$(".axis.south .grid line").length).toBe(0);
        });

        xit("renders ytick lines by default", function() {
            expect(this.view.$(".axis.west .grid line").length).toBeGreaterThan(1);
        });
    })
});

<|endoftext|>