<commit_before>var timeTick = new Deps.Dependency();

Meteor.setInterval(function () {
  timeTick.changed();
}, 1000);

Template.petitionPage.helpers({
  'responded_at': function () {
    timeTick.depend();
    return new moment(this.petition.responded_at).fromNow();
  },
  'submitted_at': function () {
    timeTick.depend();
    return new moment(this.petition.submitted).fromNow();
  },
  'initials': function () {
    return Meteor.users.find({
      '_id': {
        $in: this.petition.upvoters
      }
    }).fetch().map(function (user) {
      if (!user.profile.initials) {
        return "Witheld";
      } else {
        return user.profile.initials;
      }
    });
  }
});

Template.petitionPage.events({
  'click #approve' : function(e){
    e.preventDefault();
    var _id = this.petition._id
    Meteor.call('changePendingPetition', _id, true, function(error) {
      if (error) {
        throwError(error.reason);
      } else {
        throwError(result);
      }
    });
  }
});

Template.petitionPage.rendered = function () {
  Deps.autorun( function () {
    var petition = Petitions.findOne();
    $('.petition-status').tooltip('destroy');
    $('.petition-status').tooltip({title: Template.petitionPage.__helpers[" petitionStatus"]().description});
    $('.petition-expires').tooltip();
  });
}
<commit_msg>Resolved minor presentational annoyances<commit_after>var timeTick = new Deps.Dependency();

Meteor.setInterval(function () {
  timeTick.changed();
}, 1000);

Template.petitionPage.helpers({
  'responded_at': function () {
    timeTick.depend();
    return new moment(this.petition.responded_at).fromNow();
  },
  'submitted_at': function () {
    timeTick.depend();
    return new moment(this.petition.submitted).fromNow();
  },
  'initials': function () {
    return Meteor.users.find({
      '_id': {
        $in: this.petition.upvoters
      }
    }).fetch().map(function (user) {
      if (!user.profile.initials) {
        return "Unknown";
      } else {
        return user.profile.initials;
      }
    });
  }
});

Template.petitionPage.events({
  'click #approve' : function(e){
    e.preventDefault();
    var _id = this.petition._id
    Meteor.call('changePendingPetition', _id, true, function(error) {
      if (error) {
        throwError(error.reason);
      } else {
        throwError(result);
      }
    });
  }
});
<|endoftext|>


<commit_before>angular.module('cardcast.main', [
  'ngSanitize'
])

.controller('MainCtrl', function($scope, $location, $http, Service) {

  $scope.deck = {};
  $scope.currentCard = {};
  $scope.showWarning = false;

  var applicationID = DEV_APP_ID;
  var namespace = 'urn:x-cast:pegatech.card.cast';
  var session = null;
  var isCasting = false;

  var onInitSuccess = function() {
    console.log('Successful initialization');
  };

  var onError = function(message) {
    console.log('onError: ' + JSON.stringify(message));
  };

  var onSuccess = function(message) {
    console.log('onSuccess: ' + message);
  };

  var onStopAppSuccess = function() {
    console.log('Successful stop');
  };

  var sessionUpdateListener = function(isAlive) {
    var message = isAlive ? 'Session Updated' : 'Session Removed';
    message += ': ' + session.sessionId;
    console.log(message);
    if (!isAlive) {
      session = null;
    }
  };

  var receiverMessage = function(namespace, message) {
    isCasting = message === 'isCasting' ? true : false;
    console.log('function entered');
    console.log('receiverMessage: ' + namespace + ', ' + message);
  };

  var receiverListener = function(event) {
    if (event === 'available') {
      console.log('receiver found');
    } else {
      console.log('receiver list empty');
    }
  };

  var sessionListener = function (currentSession) {
    console.log('New session ID: ' + currentSession.sessionId);
    session = currentSession;
    session.addUpdateListener(sessionUpdateListener);
    session.addMessageListener(namespace, receiverMessage);
  };

  var initialize = function() {
    var sessionRequest = new chrome.cast.SessionRequest(applicationID);
    var apiConfig = new chrome.cast.ApiConfig(sessionRequest, sessionListener, receiverListener);

    chrome.cast.initialize(apiConfig, onInitSuccess, onError);
  };

  var stopApp = function() {
    session.stop(onStopAppSuccess, onError);
  };

  $scope.getDeck = function() {
    Service.getDeck()
      .then(function(resp) {
        $scope.deck = resp;
      });
  };

  $scope.showPopup = function(card){
    if (session === null){
      $scope.castCard(card);
    } else {
    $scope.showWarning = true;
    $scope.currentCard = card;
    }
  };

  $scope.cancelCast = function(){
    $scope.showWarning = false;
  };

  $scope.castCard = function(card) {

    var onError = function(message) {
      console.log('onError: ' + JSON.stringify(message));
    };

    var onSuccess = function(message) {
      console.log('onSuccess: ' + message);
    };

    $scope.showWarning = false;

    //*********** A Session Already Exists  ***********//
    if (session !== null) {
      if (isCasting) {

        //Give the user a chance to back out and not overwrite the card on the screen
        result = window.prompt('Someone is already casting at the moment, are you sure you want to overwrite the current card?');
        if ((result === 'y') || (result === 'Y') || (result === 'yes') || (result === 'Yes')) {
          session.sendMessage(namespace, card.card, onSuccess.bind(this, 'Message was not sent: ' + card.card), onError);
        } else {
          //If user overwites, we send _OVERWRITE and toggle isAlreadyCasting to false
          //Otherwise isAlreadyCasting will stay true to prevent message recast
          alert('overwrite canceled');
        }
      } else {
        session.sendMessage(namespace, card.card, onSuccess.bind(this, 'Message was not sent: ' + card.card), onError);
      }

    //********** A Session does not exist yet so create one ****////

    } else {

      chrome.cast.requestSession(function(currentSession) {
        session = currentSession;
        session.sendMessage(namespace, card.card, onSuccess.bind(this, 'Message sent: ' + card.card), onError);
      }, onError);
      $scope.message = '';
      $scope.show = false;
    }
  };

  $scope.deleteCard = function(card) {
    if (confirm('Are you sure you want to delete the ' + card.title + ' Card?')) {
      Service.deleteCard(card)
        .then(function(resp) {
          Service.getDeck()
            .then(function(resp) {
              $scope.deck = resp;
            });
        });
    }
  };

  $scope.sendMessage = function() {

    var onError = function(message) {
      console.log('onError: ' + JSON.stringify(message));
    };

    var onSuccess = function(message) {
      console.log('onSuccess: ' + message);
    };

    //*********** A Session Already Exists  ***********//
    if (session !== null) {
      if (session.statusText === 'isAlreadyCasting') {

        //Give the user a chance to back out and not overwrite the card on the screen
        result = window.prompt('Someone is already casting at the moment, are you sure you want to overwrite the current card?');
        if ((result === 'y') || (result === 'Y') || (result === 'yes') || (result === 'Yes')) {
          session.sendMessage(namespace, "_OVERWRITE", onSuccess.bind(this, 'Message was not sent: ' + message), onError);
        } else {
          //If user overwites, we send _OVERWRITE and toggle isAlreadyCasting to false
          //Otherwise isAlreadyCasting will stay true to prevent message recast
          alert('overwrite canceled');
        }
      }

    //********** A Session does not exist yet so create one ****////

    } else {

      chrome.cast.requestSession(function(currentSession) {
        session = currentSession;
        session.sendMessage(namespace, message, onSuccess.bind(this, 'Message sent: ' + message), onError);
      }, onError);
      $scope.message = '';
      $scope.show = false;
    }
  };

  if (!chrome.cast || !chrome.cast.isAvailable) {
    setTimeout(initialize, 1000);
  }

  $scope.getDeck();
});
<commit_msg>Testing pomander install<commit_after>angular.module('cardcast.main', [
  'ngSanitize'
])

.controller('MainCtrl', function($scope, $location, $http, Service) {

  $scope.deck = {};
  $scope.currentCard = {};
  $scope.showWarning = false;

  var applicationID = DEV_APP_ID;
  var namespace = 'urn:x-cast:pegatech.card.cast';
  var session = null;
  var isCasting = false;

  var onInitSuccess = function() {
    console.log('Successful initialization');
  };

  var onError = function(message) {
    console.log('onError: ' + JSON.stringify(message));
  };

  var onSuccess = function(message) {
    console.log('onSuccess: ' + message);
  };

  var onStopAppSuccess = function() {
    console.log('Successful stop');
  };

  var sessionUpdateListener = function(isAlive) {
    var message = isAlive ? 'Session Updated' : 'Session Removed';
    message += ': ' + session.sessionId;
    console.log(message);
    if (!isAlive) {
      session = null;
    }
  };

  var receiverMessage = function(namespace, message) {
    isCasting = message === 'isCasting' ? true : false;
    console.log('function entered');
    console.log('receiverMessage: ' + namespace + ', ' + message);
  };

  var receiverListener = function(event) {
    if (event === 'available') {
      console.log('receiver found');
    } else {
      console.log('receiver list empty');
    }
  };

  var sessionListener = function (currentSession) {
    console.log('New session ID: ' + currentSession.sessionId);
    session = currentSession;
    session.addUpdateListener(sessionUpdateListener);
    session.addMessageListener(namespace, receiverMessage);
  };

  var initialize = function() {
    var sessionRequest = new chrome.cast.SessionRequest(applicationID);
    var apiConfig = new chrome.cast.ApiConfig(sessionRequest, sessionListener, receiverListener);

    chrome.cast.initialize(apiConfig, onInitSuccess, onError);
  };

  var stopApp = function() {
    session.stop(onStopAppSuccess, onError);
  };

  $scope.getDeck = function(){
    Service.getDeck()
      .then(function(resp) {
        $scope.deck = resp;
      });
  };

  $scope.showPopup = function(card){
    if (session === null){
      $scope.castCard(card);
    } else {
    $scope.showWarning = true;
    $scope.currentCard = card;
    }
  };

  $scope.cancelCast = function(){
    $scope.showWarning = false;
  };

  $scope.castCard = function(card) {

    var onError = function(message) {
      console.log('onError: ' + JSON.stringify(message));
    };

    var onSuccess = function(message) {
      console.log('onSuccess: ' + message);
    };

    $scope.showWarning = false;

    //*********** A Session Already Exists  ***********//
    if (session !== null) {
      if (isCasting) {

        //Give the user a chance to back out and not overwrite the card on the screen
        result = window.prompt('Someone is already casting at the moment, are you sure you want to overwrite the current card?');
        if ((result === 'y') || (result === 'Y') || (result === 'yes') || (result === 'Yes')) {
          session.sendMessage(namespace, card.card, onSuccess.bind(this, 'Message was not sent: ' + card.card), onError);
        } else {
          //If user overwites, we send _OVERWRITE and toggle isAlreadyCasting to false
          //Otherwise isAlreadyCasting will stay true to prevent message recast
          alert('overwrite canceled');
        }
      } else {
        session.sendMessage(namespace, card.card, onSuccess.bind(this, 'Message was not sent: ' + card.card), onError);
      }

    //********** A Session does not exist yet so create one ****////

    } else {

      chrome.cast.requestSession(function(currentSession) {
        session = currentSession;
        session.sendMessage(namespace, card.card, onSuccess.bind(this, 'Message sent: ' + card.card), onError);
      }, onError);
      $scope.message = '';
      $scope.show = false;
    }
  };

  $scope.deleteCard = function(card) {
    if (confirm('Are you sure you want to delete the ' + card.title + ' Card?')) {
      Service.deleteCard(card)
        .then(function(resp) {
          Service.getDeck()
            .then(function(resp) {
              $scope.deck = resp;
            });
        });
    }
  };

  $scope.sendMessage = function() {

    var onError = function(message) {
      console.log('onError: ' + JSON.stringify(message));
    };

    var onSuccess = function(message) {
      console.log('onSuccess: ' + message);
    };

    //*********** A Session Already Exists  ***********//
    if (session !== null) {
      if (session.statusText === 'isAlreadyCasting') {

        //Give the user a chance to back out and not overwrite the card on the screen
        result = window.prompt('Someone is already casting at the moment, are you sure you want to overwrite the current card?');
        if ((result === 'y') || (result === 'Y') || (result === 'yes') || (result === 'Yes')) {
          session.sendMessage(namespace, "_OVERWRITE", onSuccess.bind(this, 'Message was not sent: ' + message), onError);
        } else {
          //If user overwites, we send _OVERWRITE and toggle isAlreadyCasting to false
          //Otherwise isAlreadyCasting will stay true to prevent message recast
          alert('overwrite canceled');
        }
      }

    //********** A Session does not exist yet so create one ****////

    } else {

      chrome.cast.requestSession(function(currentSession) {
        session = currentSession;
        session.sendMessage(namespace, message, onSuccess.bind(this, 'Message sent: ' + message), onError);
      }, onError);
      $scope.message = '';
      $scope.show = false;
    }
  };

  if (!chrome.cast || !chrome.cast.isAvailable) {
    setTimeout(initialize, 1000);
  }

  $scope.getDeck();
});
<|endoftext|>


<commit_before>/*jslint indent: 2, nomen: true, maxlen: 100 */
/*global _, require, db, exports */

/* From now on Frank is only a codename.
 * But don't tell it to anyone.
 * Because it is a codename, you know.
 */
var Frank,
  BaseMiddleware,
  _ = require("underscore"),
  arangodb = require("org/arangodb"),
  internal = {};

var db = arangodb.db;

internal.createUrlObject = function (url, constraint, method) {
  'use strict';
  var urlObject = {};

  if (!_.isString(url)) {
    throw "URL has to be a String";
  }

  urlObject.match = url;
  urlObject.methods = [method];

  if (!_.isNull(constraint)) {
    urlObject.constraint = constraint;
  }

  return urlObject;
};

Frank = function (options) {
  'use strict';
  var urlPrefix, templateCollection;

  options = options || {};

  urlPrefix = options.urlPrefix;
  templateCollection = options.templateCollection;

  this.routingInfo = {
    routes: []
  };

  if (_.isString(urlPrefix)) {
    this.routingInfo.urlPrefix = urlPrefix;
  }

  if (_.isString(templateCollection)) {
    this.routingInfo.templateCollection = db._collection(templateCollection) ||
      db._create(templateCollection);
  }
};

_.extend(Frank.prototype, {
  handleRequest: function (method, route, argument1, argument2) {
    'use strict';
    var newRoute = {}, options, callback;

    if (_.isUndefined(argument2)) {
      callback = argument1;
      options = {};
    } else {
      options = argument1;
      callback = argument2;
    }

    newRoute.url = internal.createUrlObject(route, options.constraint, method);
    newRoute.action = {
      callback: String(callback)
    };

    this.routingInfo.routes.push(newRoute);
  },

  head: function (route, argument1, argument2) {
    'use strict';
    this.handleRequest("head", route, argument1, argument2);
  },

  get: function (route, argument1, argument2) {
    'use strict';
    this.handleRequest("get", route, argument1, argument2);
  },

  post: function (route, argument1, argument2) {
    'use strict';
    this.handleRequest("post", route, argument1, argument2);
  },

  put: function (route, argument1, argument2) {
    'use strict';
    this.handleRequest("put", route, argument1, argument2);
  },

  patch: function (route, argument1, argument2) {
    'use strict';
    this.handleRequest("patch", route, argument1, argument2);
  },

  'delete': function (route, argument1, argument2) {
    'use strict';
    this.handleRequest("delete", route, argument1, argument2);
  }
});

BaseMiddleware = function (templateCollection) {
  'use strict';
  var middleware = function (request, response, options, next) {
    var responseFunctions;

    responseFunctions = {
      status: function (code) {
        this.responseCode = code;
      },

      set: function (key, value) {
        var attributes = {};
        if (_.isUndefined(value)) {
          attributes = key;
        } else {
          attributes[key] = value;
        }

        _.each(attributes, function (value, key) {
          key = key.toLowerCase();
          this.headers = this.headers || {};
          this.headers[key] = value;

          if (key === "content-type") {
            this.contentType = value;
          }
        }, this);
      },

      json: function (obj) {
        this.contentType = "application/json";
        this.body = JSON.stringify(obj);
      },

      render: function (templatePath, data) {
        var template;

        if (_.isUndefined(templateCollection)) {
          throw "No template collection has been provided when creating a new Frank";
        }

        template = templateCollection.firstExample({path: templatePath });

        if (_.isNull(template)) {
          throw "Template '" + templatePath + "' does not exist";
        }

        if (template.templateLanguage !== "underscore") {
          throw "Unknown template language '" + template.templateLanguage + "'";
        }

        this.body = _.template(template.content, data);
        this.contentType = template.contentType;
      }
    };

    response = _.extend(response, responseFunctions);

    next();
  };

  return middleware;
};

exports.Frank = Frank;
exports.BaseMiddleware = BaseMiddleware;
<commit_msg>Frank: Documenting Frank like I'm a Pythonista<commit_after>/*jslint indent: 2, nomen: true, maxlen: 100 */
/*global _, require, db, exports */

// `Codename Frank` is a classy way to create APIs and simple web applications
// from within **ArangoDB**.
// It is inspired by Sinatra, the classy Ruby web framework. If Frank is Sinatra,
// [ArangoDB Actions](http://www.arangodb.org/manuals/current/UserManualActions.html)
// are the corresponding `Rack`. They provide all the HTTP goodness.
//
// Please be aware of the following:
//
// > From now on Frank is only a codename.
// > But don't tell it to anyone.
// > Because it is a codename, you know.
//
// So let's get started, shall we?

// Frank uses Underscore internally. This library is wonderful.
var Frank,
  BaseMiddleware,
  _ = require("underscore"),
  db = require("org/arangodb").db,
  internal = {};

// ArangoDB uses a certain structure we refer to as `UrlObject`.
// With the following function (which is only internal, and not
// exported) you can create an UrlObject with a given URL,
// a constraint and a method. For example:
//
//     internal.createUrlObject('/rat/pack', null, 'get')
internal.createUrlObject = function (url, constraint, method) {
  'use strict';
  var urlObject = {};

  if (!_.isString(url)) {
    throw "URL has to be a String";
  }

  urlObject.match = url;
  urlObject.methods = [method];

  if (!_.isNull(constraint)) {
    urlObject.constraint = constraint;
  }

  return urlObject;
};

// ## Creating a new application
// And that's Frank. He's a constructor, so call him like this:
//
//     app = new Frank({
//       urlPrefix: "/classyroutes",
//       templateCollection: "my_templates"
//     })
//
// It takes two optional arguments as displayed above:
//
// * **The URL Prefix:** All routes you define within will be prefixed with it
// * **The Template Collection:** More information in the template section
Frank = function (options) {
  'use strict';
  var urlPrefix, templateCollection;

  options = options || {};

  urlPrefix = options.urlPrefix;
  templateCollection = options.templateCollection;

  this.routingInfo = {
    routes: []
  };

  if (_.isString(urlPrefix)) {
    this.routingInfo.urlPrefix = urlPrefix;
  }

  if (_.isString(templateCollection)) {
    this.routingInfo.templateCollection = db._collection(templateCollection) ||
      db._create(templateCollection);
  }
};

// ## The functions on a created Frank
//
// When you have created your Frank you can now define routes
// on it.
_.extend(Frank.prototype, {
  // The `handleRequest` method is the raw way to create a new
  // route. You probably wont call it directly, but it is used
  // in the other request methods:
  //
  //     app.handleRequest("get", "/come/fly/with/me", function (req, res) {
  //       //handle the request
  //     });
  handleRequest: function (method, route, argument1, argument2) {
    'use strict';
    var newRoute = {}, options, callback;

    if (_.isUndefined(argument2)) {
      callback = argument1;
      options = {};
    } else {
      options = argument1;
      callback = argument2;
    }

    newRoute.url = internal.createUrlObject(route, options.constraint, method);
    newRoute.action = {
      callback: String(callback)
    };

    this.routingInfo.routes.push(newRoute);
  },

  // ### Handle a `head` request
  // This handles requests from the HTTP verb `head`.
  // As with all other requests you can give an option as the third
  // argument, or leave it blank. You have to give a function as
  // the last argument however. It will get a request and response
  // object as its arguments
  head: function (route, argument1, argument2) {
    'use strict';
    this.handleRequest("head", route, argument1, argument2);
  },

  // ### Handle a `get` request
  // This handles requests from the HTTP verb `get`.
  // See above for the arguments you can give. An example:
  //
  //     app.get('/high/society', function (req, res) {
  //       // Take this request and deal with it!
  //     });
  get: function (route, argument1, argument2) {
    'use strict';
    this.handleRequest("get", route, argument1, argument2);
  },

  // ### Handle a `post` request
  // This handles requests from the HTTP verb `post`.
  // See above for the arguments you can give. An example:
  //
  //     app.post('/high/society', function (req, res) {
  //       // Take this request and deal with it!
  //     });
  post: function (route, argument1, argument2) {
    'use strict';
    this.handleRequest("post", route, argument1, argument2);
  },

  // ### Handle a `put` request
  // This handles requests from the HTTP verb `put`.
  // See above for the arguments you can give. An example:
  //
  //     app.put('/high/society', function (req, res) {
  //       // Take this request and deal with it!
  //     });
  put: function (route, argument1, argument2) {
    'use strict';
    this.handleRequest("put", route, argument1, argument2);
  },

  // ### Handle a `patch` request
  // This handles requests from the HTTP verb `patch`.
  // See above for the arguments you can give. An example:
  //
  //     app.patch('/high/society', function (req, res) {
  //       // Take this request and deal with it!
  //     });
  patch: function (route, argument1, argument2) {
    'use strict';
    this.handleRequest("patch", route, argument1, argument2);
  },

  // ### Handle a `delete` request
  // This handles requests from the HTTP verb `delete`.
  // See above for the arguments you can give.
  // **A word of warning:** Do not forget that `delete` is
  // a reserved word in JavaScript so call it as follows:
  //
  //     app['delete']('/high/society', function (req, res) {
  //       // Take this request and deal with it!
  //     });
  'delete': function (route, argument1, argument2) {
    'use strict';
    this.handleRequest("delete", route, argument1, argument2);
  }
});

// ## The Base Middleware
// The `BaseMiddleware` manipulates the request and response
// objects to give you a nicer API.
BaseMiddleware = function (templateCollection) {
  'use strict';
  var middleware = function (request, response, options, next) {
    var responseFunctions;

    // ### The Response Functions
    // Those are the functions added to the response objects.
    responseFunctions = {
      // ### The straightforward `status` function
      // Set the status code of your response, for example:
      //
      //     response.status(404);
      status: function (code) {
        this.responseCode = code;
      },

      // ### The radical `set` function
      // Set a header attribute, for example:
      //
      //     response.set("Content-Length", 123);
      //     response.set("Content-Type", "text/plain");
      //
      // or alternatively:
      //
      //     response.set({
      //       "Content-Length": "123",
      //       "Content-Type": "text/plain"
      //     });
      set: function (key, value) {
        var attributes = {};
        if (_.isUndefined(value)) {
          attributes = key;
        } else {
          attributes[key] = value;
        }

        _.each(attributes, function (value, key) {
          key = key.toLowerCase();
          this.headers = this.headers || {};
          this.headers[key] = value;

          if (key === "content-type") {
            this.contentType = value;
          }
        }, this);
      },

      // ### The magical `json` function
      // Set the content type to JSON and the body to the
      // JSON encoded object you provided.
      //
      //     response.json({'born': 'December 12, 1915'});
      json: function (obj) {
        this.contentType = "application/json";
        this.body = JSON.stringify(obj);
      },

      // ### The mysterious `render` function
      // If you initialize your Frank with a `templateCollection`,
      // you're in luck now.
      // It expects documents in the following form in this collection:
      //
      //     {
      //       path: "my/way",
      //       content: "hallo <%= username %>",
      //       contentType: "text/plain",
      //       templateLanguage: "underscore"
      //     }
      //
      // The `content` is the string that will be rendered by the template
      // processor. The `contentType` is the type of content that results
      // from this call. And with the `templateLanguage` you can choose
      // your template processor. There is only one choice now: `underscore`.
      //
      // If you call render, Frank will look
      // into the this collection and search by the path attribute.
      // It will then render the template with the given data:
      //
      //     response.render("my/way", {username: 'Frank'})
      //
      // Which would set the body of the response to `hello Frank` with the
      // template defined above. It will also set the `contentType` to
      // `text/plain` in this case.
      render: function (templatePath, data) {
        var template;

        if (_.isUndefined(templateCollection)) {
          throw "No template collection has been provided when creating a new Frank";
        }

        template = templateCollection.firstExample({path: templatePath });

        if (_.isNull(template)) {
          throw "Template '" + templatePath + "' does not exist";
        }

        if (template.templateLanguage !== "underscore") {
          throw "Unknown template language '" + template.templateLanguage + "'";
        }

        this.body = _.template(template.content, data);
        this.contentType = template.contentType;
      }
    };

    // Now enhance the response as described above and call next at the
    // end of this middleware (otherwise your application would never
    // be executed. Would be a shame, really).
    response = _.extend(response, responseFunctions);
    next();
  };

  return middleware;
};

// We finish off with exporting Frank and the BaseMiddleware.
// Everything else will remain our secret.
//
// Fin.
exports.Frank = Frank;
exports.BaseMiddleware = BaseMiddleware;
<|endoftext|>


<commit_before>/*
* jQuery Mobile Framework : "checkboxradio" plugin
* Copyright (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(function($, undefined ) {
$.widget( "mobile.checkboxradio", $.mobile.widget, {
	options: {
		theme: null
	},
	_create: function(){
		var self = this,
			input = this.element,
			//NOTE: Windows Phone could not find the label through a selector
			//filter works though.
			label = input.closest("form,fieldset,:jqmData(role='page')").find("label").filter('[for="' + input[0].id + '"]'),
			inputtype = input.attr( "type" ),
			checkedClass = "ui-"+ inputtype + "-on",
			uncheckedClass = "ui-"+ inputtype + "-off",
			checkedicon = "ui-icon-" + inputtype + "-on",
			uncheckedicon = "ui-icon-" + inputtype + "-off";

		if ( inputtype != "checkbox" && inputtype != "radio" ) { return; }

		//expose for other methods
		$.extend( this,{
			label			: label,
			inputtype		: inputtype,
			checkedClass    : checkedClass,
			uncheckedClass  : uncheckedClass,
			checkedicon		: checkedicon,
			uncheckedicon	: uncheckedicon
		});

		// If there's no selected theme...
		if( !this.options.theme ) {
			this.options.theme = this.element.jqmData( "theme" );
		}

		label
			.buttonMarkup({
				theme: this.options.theme,
				icon: this.element.parents( ":jqmData(type='horizontal')" ).length ? undefined : uncheckedicon,
				shadow: false
			});

		// wrap the input + label in a div
		input
			.add( label )
			.wrapAll( "

" );

		label.bind({
			vmouseover: function() {
				if( $(this).parent().is('.ui-disabled') ){ return false; }
			},

			vclick: function( event ){
				if ( input.is( ":disabled" ) ){
					event.preventDefault();
					return;
				}

				self._cacheVals();

				input.prop( "checked", inputtype === "radio" && true || !(input.prop("checked")) );

				// input set for common radio buttons will contain all the radio
				// buttons, but will not for checkboxes. clearing the checked status
				// of other radios ensures the active button state is applied properly
				self._getInputSet().not(input).prop('checked', false);

				self._updateAll();
				return false;
			}

		});

		input
			.bind({
				vmousedown: function(){
					this._cacheVals();
				},

				vclick: function(){
          // adds checked attribute to checked input when keyboard is used
          if ($(this).is(":checked")) {
             $(this).prop( "checked", true);
             self._getInputSet().not($(this)).prop('checked', false);
          } else {
             $(this).prop("checked", false);
          }
					self._updateAll();
				},

				focus: function() {
					label.addClass( "ui-focus" );
				},

				blur: function() {
					label.removeClass( "ui-focus" );
				}
			});

		this.refresh();

	},

	_cacheVals: function(){
		this._getInputSet().each(function(){
			$(this).jqmData("cacheVal", $(this).is(":checked") );
		});
	},

	//returns either a set of radios with the same name attribute, or a single checkbox
	_getInputSet: function(){
		return this.element.closest( "form,fieldset,:jqmData(role='page')" )
				.find( "input[name='"+ this.element.attr( "name" ) +"'][type='"+ this.inputtype +"']" );
	},

	_updateAll: function(){
		var self = this;

		this._getInputSet().each(function(){
			if( $(this).is(":checked") || self.inputtype === "checkbox" ){
				$(this).trigger("change");
			}
		})
		.checkboxradio( "refresh" );
	},

	refresh: function( ){
		var input = this.element,
			label = this.label,
			icon = label.find( ".ui-icon" );

		// input[0].checked expando doesn't always report the proper value
		// for checked='checked'
		if ( $(input[0]).prop('checked') ) {
			label.addClass( this.checkedClass ).removeClass( this.uncheckedClass );
			icon.addClass( this.checkedicon ).removeClass( this.uncheckedicon );

		} else {
			label.removeClass( this.checkedClass ).addClass( this.uncheckedClass );
			icon.removeClass( this.checkedicon ).addClass( this.uncheckedicon );
		}

		if( input.is( ":disabled" ) ){
			this.disable();
		}
		else {
			this.enable();
		}
	},

	disable: function(){
		this.element.prop("disabled",true).parent().addClass("ui-disabled");
	},

	enable: function(){
		this.element.prop("disabled",false).parent().removeClass("ui-disabled");
	}
});
})( jQuery );
<commit_msg>Fixed a glitch in the icon class name generation. It was adding ui-icon-ui-icon-checkbox-off to the checkbox icon element (2x ui-icon) and similar to radio icon element<commit_after>/*
* jQuery Mobile Framework : "checkboxradio" plugin
* Copyright (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(function($, undefined ) {
$.widget( "mobile.checkboxradio", $.mobile.widget, {
	options: {
		theme: null
	},
	_create: function(){
		var self = this,
			input = this.element,
			//NOTE: Windows Phone could not find the label through a selector
			//filter works though.
			label = input.closest("form,fieldset,:jqmData(role='page')").find("label").filter('[for="' + input[0].id + '"]'),
			inputtype = input.attr( "type" ),
			checkedState = inputtype + "-on",
			uncheckedState = inputtype + "-off",
			checkedClass = "ui-"+ checkedState,
			uncheckedClass = "ui-"+ uncheckedState,
			checkedicon = "ui-icon-" + checkedState,
			uncheckedicon = "ui-icon-" + uncheckedState;

		if ( inputtype != "checkbox" && inputtype != "radio" ) { return; }

		//expose for other methods
		$.extend( this,{
			label			: label,
			inputtype		: inputtype,
			checkedClass    : checkedClass,
			uncheckedClass  : uncheckedClass,
			checkedicon		: checkedicon,
			uncheckedicon	: uncheckedicon
		});

		// If there's no selected theme...
		if( !this.options.theme ) {
			this.options.theme = this.element.jqmData( "theme" );
		}

		label
			.buttonMarkup({
				theme: this.options.theme,
				icon: this.element.parents( ":jqmData(type='horizontal')" ).length ? undefined : uncheckedState,
				shadow: false
			});

		// wrap the input + label in a div
		input
			.add( label )
			.wrapAll( "

" );

		label.bind({
			vmouseover: function() {
				if( $(this).parent().is('.ui-disabled') ){ return false; }
			},

			vclick: function( event ){
				if ( input.is( ":disabled" ) ){
					event.preventDefault();
					return;
				}

				self._cacheVals();

				input.prop( "checked", inputtype === "radio" && true || !(input.prop("checked")) );

				// input set for common radio buttons will contain all the radio
				// buttons, but will not for checkboxes. clearing the checked status
				// of other radios ensures the active button state is applied properly
				self._getInputSet().not(input).prop('checked', false);

				self._updateAll();
				return false;
			}

		});

		input
			.bind({
				vmousedown: function(){
					this._cacheVals();
				},

				vclick: function(){
          // adds checked attribute to checked input when keyboard is used
          if ($(this).is(":checked")) {
             $(this).prop( "checked", true);
             self._getInputSet().not($(this)).prop('checked', false);
          } else {
             $(this).prop("checked", false);
          }
					self._updateAll();
				},

				focus: function() {
					label.addClass( "ui-focus" );
				},

				blur: function() {
					label.removeClass( "ui-focus" );
				}
			});

		this.refresh();

	},

	_cacheVals: function(){
		this._getInputSet().each(function(){
			$(this).jqmData("cacheVal", $(this).is(":checked") );
		});
	},

	//returns either a set of radios with the same name attribute, or a single checkbox
	_getInputSet: function(){
		return this.element.closest( "form,fieldset,:jqmData(role='page')" )
				.find( "input[name='"+ this.element.attr( "name" ) +"'][type='"+ this.inputtype +"']" );
	},

	_updateAll: function(){
		var self = this;

		this._getInputSet().each(function(){
			if( $(this).is(":checked") || self.inputtype === "checkbox" ){
				$(this).trigger("change");
			}
		})
		.checkboxradio( "refresh" );
	},

	refresh: function( ){
		var input = this.element,
			label = this.label,
			icon = label.find( ".ui-icon" );

		// input[0].checked expando doesn't always report the proper value
		// for checked='checked'
		if ( $(input[0]).prop('checked') ) {
			label.addClass( this.checkedClass ).removeClass( this.uncheckedClass );
			icon.addClass( this.checkedicon ).removeClass( this.uncheckedicon );

		} else {
			label.removeClass( this.checkedClass ).addClass( this.uncheckedClass );
			icon.removeClass( this.checkedicon ).addClass( this.uncheckedicon );
		}

		if( input.is( ":disabled" ) ){
			this.disable();
		}
		else {
			this.enable();
		}
	},

	disable: function(){
		this.element.prop("disabled",true).parent().addClass("ui-disabled");
	},

	enable: function(){
		this.element.prop("disabled",false).parent().removeClass("ui-disabled");
	}
});
})( jQuery );
<|endoftext|>


<commit_before>import getCaretCoordinates from 'textarea-caret';

import { extend } from 'flarum/extend';
import ComposerBody from 'flarum/components/ComposerBody';
import TextEditor from 'flarum/components/TextEditor';
import TextEditorButton from 'flarum/components/TextEditorButton';
import emojiMap from './generated/emojiMap.json';
import getEmojiIconCode from './helpers/getEmojiIconCode';
import KeyboardNavigatable from 'flarum/utils/KeyboardNavigatable';

import AutocompleteDropdown from './components/AutocompleteDropdown';

export default function addComposerAutocomplete() {
  const emojiKeys = Object.keys(emojiMap);

  extend(ComposerBody.prototype, 'config', function(original, isInitialized) {
    if (isInitialized) return;

    const composer = this;
    const $container = $('

');
    const dropdown = new AutocompleteDropdown({items: []});
    const $textarea = this.$('textarea').wrap('

');
    let emojiStart;
    let typed;

    const applySuggestion = function(replacement) {
      const insert = replacement + ' ';

      const content = composer.content();
      composer.editor.setValue(content.substring(0, emojiStart - 1) + insert + content.substr($textarea[0].selectionStart));

      const index = emojiStart - 1 + insert.length;
      composer.editor.setSelectionRange(index, index);

      dropdown.hide();
    };

    this.navigator = new KeyboardNavigatable();
    this.navigator
      .when(() => dropdown.active)
      .onUp(() => dropdown.navigate(-1))
      .onDown(() => dropdown.navigate(1))
      .onSelect(dropdown.complete.bind(dropdown))
      .onCancel(dropdown.hide.bind(dropdown))
      .bindTo($textarea);

    $textarea
      .after($container)
      .on('click keyup input', function(e) {
        // Up, down, enter, tab, escape, left, right.
        if ([9, 13, 27, 40, 38, 37, 39].indexOf(e.which) !== -1) return;

        const cursor = this.selectionStart;

        if (this.selectionEnd - cursor > 0) return;

        // Search backwards from the cursor for an ':' symbol. If we find
        // one and followed by a whitespace, we will want to show the
        // autocomplete dropdown!
        const value = this.value;
        emojiStart = 0;
        for (let i = cursor - 1; i >= 0; i--) {
          const character = value.substr(i, 1);
          // check what user typed, emoji names only contains alphanumeric,
          // underline, '+' and '-'
          if (!/[a-z0-9]|\+|\-|_|\:/.test(character)) break;
          // make sure ':' followed by a whitespace or newline
          if (character === ':' && (i == 0 || /\s/.test(value.substr(i - 1, 1)))) {
            emojiStart = i + 1;
            break;
          }
        }

        dropdown.hide();
        dropdown.active = false;

        if (emojiStart) {
          typed = value.substring(emojiStart, cursor).toLowerCase();

          const makeSuggestion = function({emoji, name, code}) {
            return (
              <button
                key={emoji}
                onclick={() => applySuggestion(emoji)}
                onmouseenter={function() {
                  dropdown.setIndex($(this).parent().index() - 1);
                }}>

                  {name}
              </button>
            );
          };

          const buildSuggestions = () => {
            const similarEmoji = [];

            // Build a regular expression to do a fuzzy match of the given input string
            const fuzzyRegexp = function(str) {
              const reEscape = new RegExp('\\(([' + ('+.*?[]{}()^$|\\'.replace(/(.)/g, '\\$1')) + '])\\)', 'g');
              return new RegExp('(.*)' + (str.toLowerCase().replace(/(.)/g, '($1)(.*?)')).replace(reEscape, '(\\$1)') + '$', 'i');
            };
            const regTyped = fuzzyRegexp(typed);

            let maxSuggestions = 7;

            const findMatchingEmojis = matcher => {
              for (let i = 0; i < emojiKeys.length && maxSuggestions > 0; i++) {
                const curEmoji = emojiKeys[i];

                if (similarEmoji.indexOf(curEmoji) === -1) {
                  const names = emojiMap[curEmoji];
                  for (let name of names) {
                    if (matcher(name)) {
                      --maxSuggestions;
                      similarEmoji.push(curEmoji);
                      break;
                    }
                  }
                }
              }
            };

            // First, try to find all emojis starting with the given string
            findMatchingEmojis(emoji => emoji.indexOf(typed) === 0);

            // If there are still suggestions left, try for some fuzzy matches
            findMatchingEmojis(emoji => regTyped.test(emoji));

            const suggestions = similarEmoji.map(emoji => ({
                emoji,
                name: emojiMap[emoji][0],
                code: getEmojiIconCode(emoji),
              })).map(makeSuggestion);

            if (suggestions.length) {
              dropdown.props.items = suggestions;
              m.render($container[0], dropdown.render());

              dropdown.show();
              const coordinates = getCaretCoordinates(this, emojiStart);
              const width = dropdown.$().outerWidth();
              const height = dropdown.$().outerHeight();
              const parent = dropdown.$().offsetParent();
              let left = coordinates.left;
              let top = coordinates.top + 15;
              if (top + height > parent.height()) {
                top = coordinates.top - height - 15;
              }
              if (left + width > parent.width()) {
                left = parent.width() - width;
              }
              top = Math.max(-$(this).offset().top, top);
              left = Math.max(-$(this).offset().left, left);
              dropdown.show(left, top);
            }
          };

          buildSuggestions();

          dropdown.setIndex(0);
          dropdown.$().scrollTop(0);
          dropdown.active = true;
        }
      });
  });

  extend(TextEditor.prototype, 'toolbarItems', function(items) {
    items.add('emoji', (
      <TextEditorButton onclick={() => this.insertAtCursor(':')} icon="far fa-smile">
        {app.translator.trans('flarum-emoji.forum.composer.emoji_tooltip')}
      </TextEditorButton>
    ));
  });
}
<commit_msg>Fix cursor not being placed correctly after emoji insertion<commit_after>import getCaretCoordinates from 'textarea-caret';

import { extend } from 'flarum/extend';
import ComposerBody from 'flarum/components/ComposerBody';
import TextEditor from 'flarum/components/TextEditor';
import TextEditorButton from 'flarum/components/TextEditorButton';
import emojiMap from './generated/emojiMap.json';
import getEmojiIconCode from './helpers/getEmojiIconCode';
import KeyboardNavigatable from 'flarum/utils/KeyboardNavigatable';

import AutocompleteDropdown from './components/AutocompleteDropdown';

export default function addComposerAutocomplete() {
  const emojiKeys = Object.keys(emojiMap);

  extend(ComposerBody.prototype, 'config', function(original, isInitialized) {
    if (isInitialized) return;

    const composer = this;
    const $container = $('

');
    const dropdown = new AutocompleteDropdown({items: []});
    const $textarea = this.$('textarea').wrap('

');
    let emojiStart;
    let typed;

    const applySuggestion = function(replacement) {
      const insert = replacement + ' ';

      // When calling setValue(), emojiStart will be set back to 0 so we need to compute this beforehand
      const index = emojiStart - 1 + insert.length;

      const content = composer.content();
      composer.editor.setValue(content.substring(0, emojiStart - 1) + insert + content.substr($textarea[0].selectionStart));

      composer.editor.setSelectionRange(index, index);

      dropdown.hide();
    };

    this.navigator = new KeyboardNavigatable();
    this.navigator
      .when(() => dropdown.active)
      .onUp(() => dropdown.navigate(-1))
      .onDown(() => dropdown.navigate(1))
      .onSelect(dropdown.complete.bind(dropdown))
      .onCancel(dropdown.hide.bind(dropdown))
      .bindTo($textarea);

    $textarea
      .after($container)
      .on('click keyup input', function(e) {
        // Up, down, enter, tab, escape, left, right.
        if ([9, 13, 27, 40, 38, 37, 39].indexOf(e.which) !== -1) return;

        const cursor = this.selectionStart;

        if (this.selectionEnd - cursor > 0) return;

        // Search backwards from the cursor for an ':' symbol. If we find
        // one and followed by a whitespace, we will want to show the
        // autocomplete dropdown!
        const value = this.value;
        emojiStart = 0;
        for (let i = cursor - 1; i >= 0; i--) {
          const character = value.substr(i, 1);
          // check what user typed, emoji names only contains alphanumeric,
          // underline, '+' and '-'
          if (!/[a-z0-9]|\+|\-|_|\:/.test(character)) break;
          // make sure ':' followed by a whitespace or newline
          if (character === ':' && (i == 0 || /\s/.test(value.substr(i - 1, 1)))) {
            emojiStart = i + 1;
            break;
          }
        }

        dropdown.hide();
        dropdown.active = false;

        if (emojiStart) {
          typed = value.substring(emojiStart, cursor).toLowerCase();

          const makeSuggestion = function({emoji, name, code}) {
            return (
              <button
                key={emoji}
                onclick={() => applySuggestion(emoji)}
                onmouseenter={function() {
                  dropdown.setIndex($(this).parent().index() - 1);
                }}>

                  {name}
              </button>
            );
          };

          const buildSuggestions = () => {
            const similarEmoji = [];

            // Build a regular expression to do a fuzzy match of the given input string
            const fuzzyRegexp = function(str) {
              const reEscape = new RegExp('\\(([' + ('+.*?[]{}()^$|\\'.replace(/(.)/g, '\\$1')) + '])\\)', 'g');
              return new RegExp('(.*)' + (str.toLowerCase().replace(/(.)/g, '($1)(.*?)')).replace(reEscape, '(\\$1)') + '$', 'i');
            };
            const regTyped = fuzzyRegexp(typed);

            let maxSuggestions = 7;

            const findMatchingEmojis = matcher => {
              for (let i = 0; i < emojiKeys.length && maxSuggestions > 0; i++) {
                const curEmoji = emojiKeys[i];

                if (similarEmoji.indexOf(curEmoji) === -1) {
                  const names = emojiMap[curEmoji];
                  for (let name of names) {
                    if (matcher(name)) {
                      --maxSuggestions;
                      similarEmoji.push(curEmoji);
                      break;
                    }
                  }
                }
              }
            };

            // First, try to find all emojis starting with the given string
            findMatchingEmojis(emoji => emoji.indexOf(typed) === 0);

            // If there are still suggestions left, try for some fuzzy matches
            findMatchingEmojis(emoji => regTyped.test(emoji));

            const suggestions = similarEmoji.map(emoji => ({
                emoji,
                name: emojiMap[emoji][0],
                code: getEmojiIconCode(emoji),
              })).map(makeSuggestion);

            if (suggestions.length) {
              dropdown.props.items = suggestions;
              m.render($container[0], dropdown.render());

              dropdown.show();
              const coordinates = getCaretCoordinates(this, emojiStart);
              const width = dropdown.$().outerWidth();
              const height = dropdown.$().outerHeight();
              const parent = dropdown.$().offsetParent();
              let left = coordinates.left;
              let top = coordinates.top + 15;
              if (top + height > parent.height()) {
                top = coordinates.top - height - 15;
              }
              if (left + width > parent.width()) {
                left = parent.width() - width;
              }
              top = Math.max(-$(this).offset().top, top);
              left = Math.max(-$(this).offset().left, left);
              dropdown.show(left, top);
            }
          };

          buildSuggestions();

          dropdown.setIndex(0);
          dropdown.$().scrollTop(0);
          dropdown.active = true;
        }
      });
  });

  extend(TextEditor.prototype, 'toolbarItems', function(items) {
    items.add('emoji', (
      <TextEditorButton onclick={() => this.insertAtCursor(':')} icon="far fa-smile">
        {app.translator.trans('flarum-emoji.forum.composer.emoji_tooltip')}
      </TextEditorButton>
    ));
  });
}
<|endoftext|>


<commit_before>// Copyright 2018, University of Colorado Boulder

/**
 * Displays 'NO REAL ROOTS', used when a quadratic has no real roots.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( require => {
  'use strict';

  // modules
  const Bounds2 = require( 'DOT/Bounds2' );
  const DerivedProperty = require( 'AXON/DerivedProperty' );
  const GQColors = require( 'GRAPHING_QUADRATICS/common/GQColors' );
  const graphingQuadratics = require( 'GRAPHING_QUADRATICS/graphingQuadratics' );
  const Node = require( 'SCENERY/nodes/Node' );
  const PhetFont = require( 'SCENERY_PHET/PhetFont' );
  const Rectangle = require( 'SCENERY/nodes/Rectangle' );
  const Tandem = require( 'TANDEM/Tandem' );
  const Text = require( 'SCENERY/nodes/Text' );

  // strings
  const noRealRootsString = require( 'string!GRAPHING_QUADRATICS/noRealRoots' );

  class NoRealRootsNode extends Node {

    /**
     * @param {BooleanProperty} rootsVisibleProperty
     * @param {BooleanProperty} vertexVisibleProperty
     * @param {BooleanProperty} coordinatesVisibleProperty
     * @param {Property.<Quadratic>} quadraticProperty - the interactive quadratic
     * @param {ModelViewTransform2} modelViewTransform
     * @param {Object} [options]
     */
    constructor( rootsVisibleProperty, vertexVisibleProperty, coordinatesVisibleProperty,
                 quadraticProperty, modelViewTransform, options ) {

      options = _.extend( {
        tandem: Tandem.required,
        phetioReadOnly: true
      }, options );

      const textNode = new Text( noRealRootsString, {
        font: new PhetFont( { size: 18, weight: 'bold' } ),
        fill: 'white',
        maxWidth: 300 // determined empirically
      } );

      const backgroundNode = new Rectangle( textNode.bounds.dilatedXY( 5, 1 ), {
        fill: GQColors.ROOTS,
        opacity: 0.75,
        cornerRadius: 4,
        center: textNode.center
      } );

      assert && assert( !options.children, 'NoRealRootsNode sets children' );
      options.children = [ backgroundNode, textNode ];

      super( options );

      // Part of the graph where 'NO REAL ROOTS' may overlap with vertex coordinates.
      // when 'NO REAL ROOTS' is typically centered at the origin.
      // See https://github.com/phetsims/graphing-quadratics/issues/88
      const vertexOverlapBounds = new Bounds2(
        modelViewTransform.viewToModelDeltaX( -this.width / 2 ), -1,
        modelViewTransform.viewToModelDeltaX( this.width / 2 ), 1 );

      // The center of this Node, typically at the origin, except when that would overlap the vertex's coordinates.
      // In that case, position above or below the x axis, depending on which way the parabola opens.
      // See https://github.com/phetsims/graphing-quadratics/issues/88
      const centerProperty = new DerivedProperty(
        [ vertexVisibleProperty, coordinatesVisibleProperty, quadraticProperty ],
        ( vertexVisible, coordinatesVisible, quadratic ) => {
          if ( vertexVisible && // the Vertex checkbox is checked
               coordinatesVisible && // the Coordinates checkbox is checked
               ( quadratic.roots && quadratic.roots.length === 0 ) && // no roots
               // vertex is in a position where its coordinates will overlap
               ( quadratic.vertex && vertexOverlapBounds.containsPoint( quadratic.vertex ) ) ) {
            // center above or below the x axis
            return modelViewTransform.modelToViewXY( 0, ( quadratic.a > 0 ? -1 : 1 ) );
          }
          else {
            // center at the origin
            return modelViewTransform.modelToViewXY( 0, 0 );
          }
        }
      );
      centerProperty.linkAttribute( this, 'center' );

      const visibleProperty = new DerivedProperty(
        [ rootsVisibleProperty, quadraticProperty ],
        ( rootsVisible, quadratic ) =>
          rootsVisible && // the Roots checkbox is checked
          !!( quadratic.roots && quadratic.roots.length === 0 ) // the interactive quadratic has no roots
        );
      visibleProperty.linkAttribute( this, 'visible' );
    }
  }

  return graphingQuadratics.register( 'NoRealRootsNode', NoRealRootsNode );
} );<commit_msg>doc<commit_after>// Copyright 2018, University of Colorado Boulder

/**
 * Displays 'NO REAL ROOTS', used when a quadratic has no real roots.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( require => {
  'use strict';

  // modules
  const Bounds2 = require( 'DOT/Bounds2' );
  const DerivedProperty = require( 'AXON/DerivedProperty' );
  const GQColors = require( 'GRAPHING_QUADRATICS/common/GQColors' );
  const graphingQuadratics = require( 'GRAPHING_QUADRATICS/graphingQuadratics' );
  const Node = require( 'SCENERY/nodes/Node' );
  const PhetFont = require( 'SCENERY_PHET/PhetFont' );
  const Rectangle = require( 'SCENERY/nodes/Rectangle' );
  const Tandem = require( 'TANDEM/Tandem' );
  const Text = require( 'SCENERY/nodes/Text' );

  // strings
  const noRealRootsString = require( 'string!GRAPHING_QUADRATICS/noRealRoots' );

  class NoRealRootsNode extends Node {

    /**
     * @param {BooleanProperty} rootsVisibleProperty
     * @param {BooleanProperty} vertexVisibleProperty
     * @param {BooleanProperty} coordinatesVisibleProperty
     * @param {Property.<Quadratic>} quadraticProperty - the interactive quadratic
     * @param {ModelViewTransform2} modelViewTransform
     * @param {Object} [options]
     */
    constructor( rootsVisibleProperty, vertexVisibleProperty, coordinatesVisibleProperty,
                 quadraticProperty, modelViewTransform, options ) {

      options = _.extend( {
        tandem: Tandem.required,
        phetioReadOnly: true
      }, options );

      const textNode = new Text( noRealRootsString, {
        font: new PhetFont( { size: 18, weight: 'bold' } ),
        fill: 'white',
        maxWidth: 300 // determined empirically
      } );

      const backgroundNode = new Rectangle( textNode.bounds.dilatedXY( 5, 1 ), {
        fill: GQColors.ROOTS,
        opacity: 0.75,
        cornerRadius: 4,
        center: textNode.center
      } );

      assert && assert( !options.children, 'NoRealRootsNode sets children' );
      options.children = [ backgroundNode, textNode ];

      super( options );

      // Part of the graph where 'NO REAL ROOTS' may overlap with vertex coordinates, when 'NO REAL ROOTS' is
      // typically centered at the origin. Width is based on translated string width, height was determined
      // empirically. See https://github.com/phetsims/graphing-quadratics/issues/88
      const vertexOverlapBounds = new Bounds2(
        modelViewTransform.viewToModelDeltaX( -this.width / 2 ), -1,
        modelViewTransform.viewToModelDeltaX( this.width / 2 ), 1 );

      // The center of this Node, typically at the origin, except when that would overlap the vertex's coordinates.
      // In that case, position above or below the x axis, depending on which way the parabola opens.
      // See https://github.com/phetsims/graphing-quadratics/issues/88
      const centerProperty = new DerivedProperty(
        [ vertexVisibleProperty, coordinatesVisibleProperty, quadraticProperty ],
        ( vertexVisible, coordinatesVisible, quadratic ) => {
          if ( vertexVisible && // the Vertex checkbox is checked
               coordinatesVisible && // the Coordinates checkbox is checked
               ( quadratic.roots && quadratic.roots.length === 0 ) && // no roots
               // vertex is in a position where its coordinates will overlap
               ( quadratic.vertex && vertexOverlapBounds.containsPoint( quadratic.vertex ) ) ) {
            // center above or below the x axis
            return modelViewTransform.modelToViewXY( 0, ( quadratic.a > 0 ? -1 : 1 ) );
          }
          else {
            // center at the origin
            return modelViewTransform.modelToViewXY( 0, 0 );
          }
        }
      );
      centerProperty.linkAttribute( this, 'center' );

      const visibleProperty = new DerivedProperty(
        [ rootsVisibleProperty, quadraticProperty ],
        ( rootsVisible, quadratic ) =>
          rootsVisible && // the Roots checkbox is checked
          !!( quadratic.roots && quadratic.roots.length === 0 ) // the interactive quadratic has no roots
        );
      visibleProperty.linkAttribute( this, 'visible' );
    }
  }

  return graphingQuadratics.register( 'NoRealRootsNode', NoRealRootsNode );
} );<|endoftext|>


<commit_before>var motionbarchart = function (userConfig) {
  d3 = dex.charts.d3.d3v3;
  var chart;

  var color = d3.scale.category20c();

  var defaults = {
    // The parent container of this chart.
    'parent': '#MotionBarChartParent',
    // Set these when you need to CSS style components independently.
    'id': 'MotionBarhartId',
    'class': 'MotionBarChartClass',
    // Our data...
    'csv': undefined,
    'resizable': true,
    // Tells us which columns represent what.
    'index': {
      'name': 0,
      'color': 0,
      'time': 1,
      'y': 2
    },
    // Chart dimensions.
    'width': '100%',
    'height': '100%',
    'margin': {
      top: 50,
      right: 120,
      bottom: 180,
      left: 150
    },

    'bar': dex.config.rectangle({
        'color': function (d, i) {
          return color(i);
        },
        'stroke.width': 1,
        'stroke.color': 'black',
        'events': {
          'mouseover': function () {
            d3.select(this)
              .style("stroke", 'red')
              .style("stroke-width", 2);
          },
          'mouseout': function () {
            d3.select(this)
              .style("stroke", chart.config.bar.stroke.color)
              .style("stroke-width", chart.config.bar.stroke.width);
          }
        }
      }
    ),

    // Main label configuration
    'label.font.size': 64,
    'label.fill.fillColor': 'steelblue',
    'label.fill.fillOpacity': 0.4,
    'label.y': function (d) {
      return chart.config.margin.top;
    },
    'label.dy': '.50em',
    'label.x': function (d) {
      return (chart.config.width - chart.config.margin.left - chart.config.margin.right) / 2;
    },

    'transform': '',
    'duration': 5000,

    'xaxis': dex.config.axis({
      'scale.type': 'linear',
      'orient': 'bottom',
      'label': dex.config.text({
        'anchor': 'start',
        'writingMode': 'tb',
        'dx': function (d) {
          return (chart.config.bar.width) / 2;
        },
        'dy': '.5em'
      }),
      'tick.stroke.color': 'black',
      'tick.stroke.width': 1,
      'tick.fill.fillColor': 'none',
      'axisLine.stroke.color': 'black',
      'axisLine.stroke.width': 1,
      'axisLine.stroke.dasharray': "0",
      'axisLine.fill.fillColor': 'none'
    }),
    'yaxis': dex.config.axis({
      'scale.type': 'linear',
      'orient': 'left',
      'label': dex.config.text({
        'anchor': 'end',
        'dx': '-.5em' //function(d) { return chart.config.margin.left; },
      }),
      'title': dex.config.text(),
      'tick.stroke.width': 1,
      'tick.fill.fillColor': 'none',
      'axisLine.stroke.color': 'black',
      'axisLine.stroke.width': 2,
      'axisLine.stroke.dasharray': "10 10",
      'axisLine.fill.fillColor': 'none'
    })
  };

  var chart = new dex.component(userConfig, defaults);

  chart.render = function render() {
    d3 = dex.charts.d3.d3v3;
    return chart.resize();
  };

  chart.update = function update() {
    d3 = dex.charts.d3.d3v3;
    var config = chart.config;
    var margin = config.margin;
    var csv = config.csv;

    var width = config.width - margin.left - margin.right;
    var height = config.height - margin.top - margin.bottom;

    d3.selectAll(config.parent).selectAll('*').remove();

    var keyMap = {};

    csv.data.forEach(function (row) {
      var curName = row[config.index.name];
      var curColor = row[config.index.color];
      var curTime = row[config.index.time];
      var curY = row[config.index.y];
      var curSize = +row[config.index.size];

      if (!keyMap[curName]) {
        keyMap[curName] = {
          'name': curName,
          'color': curColor,
          'time': curTime,
          'y': [[curTime, curY]],
          'size': [[curTime, curSize]]
        };
      }
      else {
        keyMap[curName].y.push([curTime, curY]);
        keyMap[curName].size.push([curTime, curSize]);
      }
    });

    var uniques = dex.matrix.uniques(csv.data);

    var timeExtents = dex.matrix.extent(csv.data, [config.index.time]);
    //var xExtents = [0, uniques[config.index.name].length-1];
    var yExtents = dex.matrix.extent(csv.data, [config.index.y]);

    //dex.console.log("EXTENTS: Y", yExtents, "UNIQUES", uniques[config.index.name]);

    // Various scales. These domains make assumptions of data, naturally.
    var xScale = d3.scale.ordinal()
      .domain(uniques[config.index.name].sort())
      .rangePoints([0, width]);

    //  d3.scale.linear().domain(xExtents).range([0, width - 60]);
    var yScale = dex.config.createScale(config.yaxis.scale)
      .domain([0, yExtents[1]]).range([height, 0]);

    // The x & y axes.
    var xAxis = dex.config.createAxis(config.xaxis)
      .scale(xScale);

    var yAxis = dex.config.createAxis(config.yaxis)
      .scale(yScale);

    var svg = d3.select(config.parent)
      .append("svg")
      .attr("id", config["id"])
      .attr("class", config["class"])
      .attr('width', config.width)
      .attr('height', config.height);

    var rootG = svg
      .append('g')
      .attr('transform', 'translate(' +
        margin.left + ',' + margin.top + ') ' +
        config.transform);

    // Add the x-axis.
    rootG.append("g")
      .attr("class", "xaxis")
      .attr('width', width)
      .attr('height', height)
      .attr("transform", "translate(0," + height + ")")
      .call(xAxis);

    // Add the y-axis.
    rootG.append("g")
      .attr("class", "yaxis")
      .call(yAxis);

    var xticks = rootG.selectAll(".xaxis .tick");

    var xtickLines = xticks.selectAll("line")
      .call(dex.config.configureStroke, config.xaxis.tick.stroke)
      .call(dex.config.configureFill, config.xaxis.tick.fill);

    var yticks = rootG.selectAll(".yaxis .tick");

    var yTickLines = yticks.selectAll("line")
      .call(dex.config.configureStroke, config.yaxis.tick.stroke)
      .call(dex.config.configureFill, config.yaxis.tick.fill);

    rootG.selectAll(".xaxis path")
      .call(dex.config.configureStroke, config.xaxis.axisLine.stroke)
      .call(dex.config.configureFill, config.xaxis.axisLine.fill);

    rootG.selectAll(".yaxis path")
      .call(dex.config.configureStroke, config.yaxis.axisLine.stroke)
      .call(dex.config.configureFill, config.yaxis.axisLine.fill);

    //var xTickLabels = xticks
    //  .append("text")
    //  .text("P" + config.csv.header[config.index.name]);

    //dex.console.log("XTICK-LABELS", xTickLabels);
    xticks.selectAll("text")
      .call(dex.config.configureText, config.xaxis.label);

    //xticks.selectAll("text")
    //  .call(dex.config.configureText, config.xaxis.label);

    // Add a y-axis label.
    rootG.append("text")
      .attr("class", "yLabel")
      .call(dex.config.configureText, config.yaxis.title)
      .text(config.csv.header[config.index.y]);

    yticks.selectAll("text")
      .call(dex.config.configureText, config.yaxis.label);

    // Add the year label; the value is set on transition.
    var label = rootG.append("text")
      .attr("class", "timeLabel")
      .attr("text-anchor", "end")
      .call(dex.config.configureText, config.label)
      .text(timeExtents[0]);

    // Load the data.
    //d3.json("nations.json", function (nations) {

    // A bisector since many nation's data is sparsely-defined.
    var bisect = d3.bisector(function (d) {
      return d[0];
    });

    // Add a bar per nation. Initialize the data at min year value, and set the colors.
    var bars = rootG.append("g")
      .attr("class", "bars")
      .selectAll(".bar")
      .data(interpolateData(timeExtents[0]))
      .enter().append("rect")
      .attr("class", "bar")
      .call(dex.config.configureRectangle, config.bar)
      .call(position);
    //.sort(order);

    // Add a title.
    bars
      .append("tooltip-content")
      .text(function (d, i) {
        return "
" +
          "
Name:	" + d.name + "
" +
          "
Category:	" + d.color + "
" +
          "
Value:	" + d.y + "
" +
          "
";
      });

    // Add an overlay for the year label.
    var box = label.node().getBBox();

    var overlay = rootG.append("rect")
      .attr("class", "overlay")
      .attr("x", box.x)
      .attr("y", box.y)
      .attr("width", box.width)
      .attr("height", box.height)
      .attr("fill", "none")
      .style("pointer-events", "all")
      .style("cursor", "ew-resize")
      .on("mouseover", enableInteraction);

    // Start a transition that interpolates the data based on year.
    rootG.transition()
      .duration(config.duration)
      .ease("linear")
      .tween("year", tweenYear)
      .each("end", enableInteraction);

    // Positions the dots based on data.
    function position(bar) {
      var barWidth = Math.floor(width / bar.size() - 8);

      bar
        .attr("x", function (d, i) {
          return xScale(d.name);
        })
        .attr("y", function (d) {
          return yScale(d.y);
        })
        .attr("width", function (d) {
          return barWidth;
        })
        .attr("height", function (d) {
          //console.log(d.name + ": yScale(0)=" + yScale(0) + " - yScale(" + d.y + ")=" + yScale(d.y));
          // Some values were going negative...I might be sweeping a bug under the rug
          // but this at least filters these values.
          return Math.max(yScale(0) - yScale(d.y), 0);
        });
    }

    // After the transition finishes, you can mouseover to change the year.
    function enableInteraction() {
      //dex.console.log("ENABLING INTERACTION");
      var yearScale = d3.scale.linear()
        .domain(timeExtents)
        .range([box.x + 10, box.x + box.width - 10])
        .clamp(true);

      // Cancel the current transition, if any.
      rootG.transition().duration(0);

      overlay
        .on("mouseover", mouseover)
        .on("mouseout", mouseout)
        .on("mousemove", mousemove)
        .on("touchmove", mousemove);

      function mouseover() {
        label.classed("active", true);
      }

      function mouseout() {
        label.classed("active", false);
      }

      function mousemove() {
        displayYear(yearScale.invert(d3.mouse(this)[0]));
      }
    }

    // Tweens the entire chart by first tweening the year, and then the data.
    // For the interpolated data, the dots and label are redrawn.
    function tweenYear() {
      var year = d3.interpolateNumber(timeExtents[0], timeExtents[1]);
      return function (t) {
        displayYear(year(t));
      };
    }

    // Updates the display to show the specified year.
    function displayYear(year) {
      //dex.console.log("interpolateData(" + year + ")=",
      //  interpolateData(year));
      bars.data(interpolateData(year), function (d) {
        //dex.console.log("'" + d.name + "', interpolateData(" + year + ")=",
        //  interpolateData(year));
        return d.name;
      }).call(position);//.sort(order);
      label.text(Math.round(year));
    }

    // Interpolates the dataset for the given (fractional) year.
    function interpolateData(year) {
      var timeData = [];

      //
      for (var name in keyMap) {
        if (keyMap.hasOwnProperty(name)) {
          var entry = keyMap[name];

          //dex.console.log("ENTRY-DATA", entry);
          timeData.push({
            time: year,
            name: entry.name,
            color: entry.color,
            y: interpolateValues(entry.y, year),
            size: interpolateValues(entry.size, year)
          });
        }
      }
      //dex.console.log("interpolateData(" + year + ")=", timeData);
      return timeData;
    }

    // Finds (and possibly interpolates) the value for the specified year.
    function interpolateValues(values, year) {
      //dex.console.log("VALUES", values);
      var i = bisect.left(values, year, 0, values.length - 1),
        a = values[i];
      if (i > 0) {
        var b = values[i - 1],
          t = (year - a[0]) / (b[0] - a[0]);
        return a[1] * (1 - t) + b[1] * t;
      }
      return a[1];
    }
  };

  $(document).ready(function () {

    // Add tooltips
    $(chart.config.parent).tooltip({
      items: "rect",
      content: function () {
        return $(this).find("tooltip-content").text();
      },
      track: true
    });
  });

  return chart;
};

module.exports = motionbarchart;<commit_msg>Indexes can also be header names now<commit_after>var motionbarchart = function (userConfig) {
  d3 = dex.charts.d3.d3v3;
  var chart;

  var color = d3.scale.category20c();

  var defaults = {
    // The parent container of this chart.
    'parent': '#MotionBarChartParent',
    // Set these when you need to CSS style components independently.
    'id': 'MotionBarhartId',
    'class': 'MotionBarChartClass',
    // Our data...
    'csv': undefined,
    'resizable': true,
    // Tells us which columns represent what.
    'index': {
      'name': 0,
      'color': 0,
      'time': 1,
      'y': 2
    },
    // Chart dimensions.
    'width': '100%',
    'height': '100%',
    'margin': {
      top: 50,
      right: 120,
      bottom: 180,
      left: 150
    },

    'bar': dex.config.rectangle({
        'color': function (d, i) {
          return color(i);
        },
        'stroke.width': 1,
        'stroke.color': 'black',
        'events': {
          'mouseover': function () {
            d3.select(this)
              .style("stroke", 'red')
              .style("stroke-width", 2);
          },
          'mouseout': function () {
            d3.select(this)
              .style("stroke", chart.config.bar.stroke.color)
              .style("stroke-width", chart.config.bar.stroke.width);
          }
        }
      }
    ),

    // Main label configuration
    'label.font.size': 64,
    'label.fill.fillColor': 'steelblue',
    'label.fill.fillOpacity': 0.4,
    'label.y': function (d) {
      return chart.config.margin.top;
    },
    'label.dy': '.50em',
    'label.x': function (d) {
      return (chart.config.width - chart.config.margin.left - chart.config.margin.right) / 2;
    },

    'transform': '',
    'duration': 5000,

    'xaxis': dex.config.axis({
      'scale.type': 'linear',
      'orient': 'bottom',
      'label': dex.config.text({
        'anchor': 'start',
        'writingMode': 'tb',
        'dx': function (d) {
          return (chart.config.bar.width) / 2;
        },
        'dy': '.5em'
      }),
      'tick.stroke.color': 'black',
      'tick.stroke.width': 1,
      'tick.fill.fillColor': 'none',
      'axisLine.stroke.color': 'black',
      'axisLine.stroke.width': 1,
      'axisLine.stroke.dasharray': "0",
      'axisLine.fill.fillColor': 'none'
    }),
    'yaxis': dex.config.axis({
      'scale.type': 'linear',
      'orient': 'left',
      'label': dex.config.text({
        'anchor': 'end',
        'dx': '-.5em' //function(d) { return chart.config.margin.left; },
      }),
      'title': dex.config.text(),
      'tick.stroke.width': 1,
      'tick.fill.fillColor': 'none',
      'axisLine.stroke.color': 'black',
      'axisLine.stroke.width': 2,
      'axisLine.stroke.dasharray': "10 10",
      'axisLine.fill.fillColor': 'none'
    })
  };

  var chart = new dex.component(userConfig, defaults);

  chart.render = function render() {
    d3 = dex.charts.d3.d3v3;
    return chart.resize();
  };

  chart.update = function update() {
    d3 = dex.charts.d3.d3v3;
    var config = chart.config;
    var margin = config.margin;
    var csv = config.csv;

    var width = config.width - margin.left - margin.right;
    var height = config.height - margin.top - margin.bottom;

    d3.selectAll(config.parent).selectAll('*').remove();

    var keyMap = {};

    var nameIndex = dex.csv.getColumnNumber(csv, config.index.name);
    var colorIndex = dex.csv.getColumnNumber(csv, config.index.color);
    var timeIndex = dex.csv.getColumnNumber(csv, config.index.time);
    var yIndex = dex.csv.getColumnNumber(csv, config.index.y);
    var sizeIndex = dex.csv.getColumnNumber(csv, config.index.size);

    csv.data.forEach(function (row) {
      var curName = row[nameIndex];
      var curColor = row[colorIndex];
      var curTime = row[timeIndex];
      var curY = row[yIndex];
      var curSize = +row[sizeIndex];

      if (!keyMap[curName]) {
        keyMap[curName] = {
          'name': curName,
          'color': curColor,
          'time': curTime,
          'y': [[curTime, curY]],
          'size': [[curTime, curSize]]
        };
      }
      else {
        keyMap[curName].y.push([curTime, curY]);
        keyMap[curName].size.push([curTime, curSize]);
      }
    });

    var uniques = dex.matrix.uniques(csv.data);

    var timeExtents = dex.matrix.extent(csv.data, [timeIndex]);
    //var xExtents = [0, uniques[nameIndex].length-1];
    var yExtents = dex.matrix.extent(csv.data, [yIndex]);

    //dex.console.log("EXTENTS: Y", yExtents, "UNIQUES", uniques[nameIndex]);

    // Various scales. These domains make assumptions of data, naturally.
    var xScale = d3.scale.ordinal()
      .domain(uniques[nameIndex].sort())
      .rangePoints([0, width]);

    //  d3.scale.linear().domain(xExtents).range([0, width - 60]);
    var yScale = dex.config.createScale(config.yaxis.scale)
      .domain([0, yExtents[1]]).range([height, 0]);

    // The x & y axes.
    var xAxis = dex.config.createAxis(config.xaxis)
      .scale(xScale);

    var yAxis = dex.config.createAxis(config.yaxis)
      .scale(yScale);

    var svg = d3.select(config.parent)
      .append("svg")
      .attr("id", config["id"])
      .attr("class", config["class"])
      .attr('width', config.width)
      .attr('height', config.height);

    var rootG = svg
      .append('g')
      .attr('transform', 'translate(' +
        margin.left + ',' + margin.top + ') ' +
        config.transform);

    // Add the x-axis.
    rootG.append("g")
      .attr("class", "xaxis")
      .attr('width', width)
      .attr('height', height)
      .attr("transform", "translate(0," + height + ")")
      .call(xAxis);

    // Add the y-axis.
    rootG.append("g")
      .attr("class", "yaxis")
      .call(yAxis);

    var xticks = rootG.selectAll(".xaxis .tick");

    var xtickLines = xticks.selectAll("line")
      .call(dex.config.configureStroke, config.xaxis.tick.stroke)
      .call(dex.config.configureFill, config.xaxis.tick.fill);

    var yticks = rootG.selectAll(".yaxis .tick");

    var yTickLines = yticks.selectAll("line")
      .call(dex.config.configureStroke, config.yaxis.tick.stroke)
      .call(dex.config.configureFill, config.yaxis.tick.fill);

    rootG.selectAll(".xaxis path")
      .call(dex.config.configureStroke, config.xaxis.axisLine.stroke)
      .call(dex.config.configureFill, config.xaxis.axisLine.fill);

    rootG.selectAll(".yaxis path")
      .call(dex.config.configureStroke, config.yaxis.axisLine.stroke)
      .call(dex.config.configureFill, config.yaxis.axisLine.fill);

    //var xTickLabels = xticks
    //  .append("text")
    //  .text("P" + config.csv.header[nameIndex]);

    //dex.console.log("XTICK-LABELS", xTickLabels);
    xticks.selectAll("text")
      .call(dex.config.configureText, config.xaxis.label);

    //xticks.selectAll("text")
    //  .call(dex.config.configureText, config.xaxis.label);

    // Add a y-axis label.
    rootG.append("text")
      .attr("class", "yLabel")
      .call(dex.config.configureText, config.yaxis.title)
      .text(config.csv.header[yIndex]);

    yticks.selectAll("text")
      .call(dex.config.configureText, config.yaxis.label);

    // Add the year label; the value is set on transition.
    var label = rootG.append("text")
      .attr("class", "timeLabel")
      .attr("text-anchor", "end")
      .call(dex.config.configureText, config.label)
      .text(timeExtents[0]);

    // Load the data.
    //d3.json("nations.json", function (nations) {

    // A bisector since many nation's data is sparsely-defined.
    var bisect = d3.bisector(function (d) {
      return d[0];
    });

    // Add a bar per nation. Initialize the data at min year value, and set the colors.
    var bars = rootG.append("g")
      .attr("class", "bars")
      .selectAll(".bar")
      .data(interpolateData(timeExtents[0]))
      .enter().append("rect")
      .attr("class", "bar")
      .call(dex.config.configureRectangle, config.bar)
      .call(position);
    //.sort(order);

    // Add a title.
    bars
      .append("tooltip-content")
      .text(function (d, i) {
        return "
" +
          "
Name:	" + d.name + "
" +
          "
Category:	" + d.color + "
" +
          "
Value:	" + d.y + "
" +
          "
";
      });

    // Add an overlay for the year label.
    var box = label.node().getBBox();

    var overlay = rootG.append("rect")
      .attr("class", "overlay")
      .attr("x", box.x)
      .attr("y", box.y)
      .attr("width", box.width)
      .attr("height", box.height)
      .attr("fill", "none")
      .style("pointer-events", "all")
      .style("cursor", "ew-resize")
      .on("mouseover", enableInteraction);

    // Start a transition that interpolates the data based on year.
    rootG.transition()
      .duration(config.duration)
      .ease("linear")
      .tween("year", tweenYear)
      .each("end", enableInteraction);

    // Positions the dots based on data.
    function position(bar) {
      var barWidth = Math.floor(width / bar.size() - 8);

      bar
        .attr("x", function (d, i) {
          return xScale(d.name);
        })
        .attr("y", function (d) {
          return yScale(d.y);
        })
        .attr("width", function (d) {
          return barWidth;
        })
        .attr("height", function (d) {
          //console.log(d.name + ": yScale(0)=" + yScale(0) + " - yScale(" + d.y + ")=" + yScale(d.y));
          // Some values were going negative...I might be sweeping a bug under the rug
          // but this at least filters these values.
          return Math.max(yScale(0) - yScale(d.y), 0);
        });
    }

    // After the transition finishes, you can mouseover to change the year.
    function enableInteraction() {
      //dex.console.log("ENABLING INTERACTION");
      var yearScale = d3.scale.linear()
        .domain(timeExtents)
        .range([box.x + 10, box.x + box.width - 10])
        .clamp(true);

      // Cancel the current transition, if any.
      rootG.transition().duration(0);

      overlay
        .on("mouseover", mouseover)
        .on("mouseout", mouseout)
        .on("mousemove", mousemove)
        .on("touchmove", mousemove);

      function mouseover() {
        label.classed("active", true);
      }

      function mouseout() {
        label.classed("active", false);
      }

      function mousemove() {
        displayYear(yearScale.invert(d3.mouse(this)[0]));
      }
    }

    // Tweens the entire chart by first tweening the year, and then the data.
    // For the interpolated data, the dots and label are redrawn.
    function tweenYear() {
      var year = d3.interpolateNumber(timeExtents[0], timeExtents[1]);
      return function (t) {
        displayYear(year(t));
      };
    }

    // Updates the display to show the specified year.
    function displayYear(year) {
      //dex.console.log("interpolateData(" + year + ")=",
      //  interpolateData(year));
      bars.data(interpolateData(year), function (d) {
        //dex.console.log("'" + d.name + "', interpolateData(" + year + ")=",
        //  interpolateData(year));
        return d.name;
      }).call(position);//.sort(order);
      label.text(Math.round(year));
    }

    // Interpolates the dataset for the given (fractional) year.
    function interpolateData(year) {
      var timeData = [];

      //
      for (var name in keyMap) {
        if (keyMap.hasOwnProperty(name)) {
          var entry = keyMap[name];

          //dex.console.log("ENTRY-DATA", entry);
          timeData.push({
            time: year,
            name: entry.name,
            color: entry.color,
            y: interpolateValues(entry.y, year),
            size: interpolateValues(entry.size, year)
          });
        }
      }
      //dex.console.log("interpolateData(" + year + ")=", timeData);
      return timeData;
    }

    // Finds (and possibly interpolates) the value for the specified year.
    function interpolateValues(values, year) {
      //dex.console.log("VALUES", values);
      var i = bisect.left(values, year, 0, values.length - 1),
        a = values[i];
      if (i > 0) {
        var b = values[i - 1],
          t = (year - a[0]) / (b[0] - a[0]);
        return a[1] * (1 - t) + b[1] * t;
      }
      return a[1];
    }
  };

  $(document).ready(function () {

    // Add tooltips
    $(chart.config.parent).tooltip({
      items: "rect",
      content: function () {
        return $(this).find("tooltip-content").text();
      },
      track: true
    });
  });

  return chart;
};

module.exports = motionbarchart;<|endoftext|>


<commit_before>var utils = require('../../lib/utils'),
    packages = require('../../lib/packages'),
    logger = require('../../lib/logger');

exports.summary = 'Load a project and output resulting JSON';

exports.usage = '' +
'kanso show [PATH]\n' +
'\n' +
'Parameters:\n' +
'  PATH     Path to project directory to show (defaults to ".")';

exports.run = function (settings, plugins, args) {
    var dir = utils.abspath(args[0] || '.');
    packages.load(plugins, dir, true, [], null, function (err, doc) {
        if (err) {
            return logger.error(err);
        }
        // output to console (not a log entry, actual output)
        console.log(JSON.stringify(doc, null, 4));
        logger.clean_exit = true;
    });
};
<commit_msg>update show plugin to add kanso packages path<commit_after>var utils = require('../../lib/utils'),
    packages = require('../../lib/packages'),
    logger = require('../../lib/logger'),
    couchdb = require('../../lib/couchdb'),
    argParse = require('../../lib/args').parse;

exports.summary = 'Load a project and output resulting JSON';

exports.usage = '' +
'kanso show [PATH]\n' +
'\n' +
'Parameters:\n' +
'  PATH     Path to project directory to show (defaults to ".")';

exports.run = function (settings, plugins, args) {
    var a = argParse(args, {
        'minify': {match: '--minify'},
        'minify_attachments': {match: '--minify-attachments'},
        'baseURL': {match: '--baseURL', value: true}
    });
    var dir = utils.abspath(a.positional[0] || '.');

    // suppress logger output
    logger.level = 'error';

    exports.loadApp(plugins, dir, function (err, doc, cfg) {
        if (err) {
            return logger.error(err);
        }
        logger.clean_exit = true;
        console.log(JSON.stringify(doc, null, 4));
    });
};

exports.loadApp = function (plugins, dir, callback) {
    var paths = [__dirname + '/../../packages'];
    packages.load(plugins, dir, true, paths, null, callback);
};
<|endoftext|>


<commit_before>Namespace('Enpowi').

    Class('directives', {
        Static: {
            defaultModuleElement: null,
            queue: [],
            data: {},
            app: null,
	        bindingDirectives: [],
	        setBinding: function(directive) {
		        this.bindingDirectives.push(directive);
	        },
	        doneBinding: function(directive) {
		        this.bindingDirectives.splice(this.bindingDirectives.indexOf(directive), 1);

		        if (this.bindingDirectives.length < 1) {
			        Enpowi.App.pub('directive.ready');
		        }
	        },
            setup: function (app) {
                var me = this;
                me.app = app;

                Vue.directive('module', {
                    bind: function () {
                        var el = this.el,
	                        directive = this;
	                    me.setBinding(directive);
                        switch (el.nodeName) {
                            case 'FORM':
                                Enpowi.forms.strategy(el, this.vm, this);
	                            me.doneBinding(directive);
                        }
                    }
                });

                Vue.directive('module-item', {
                    deep: true,
                    bind: function () {
                        var me = this,
	                        el = this.el;

	                    el.onchange = function () {
		                    var form,
			                    parent;

		                    parent = el.parentNode;

		                    while (parent !== null && parent.nodeName !== 'FORM') {
			                    parent = parent.parentNode;

		                    }

		                    if (parent === null) return;

		                    form = parent;

	                        Enpowi.utilities.trigger(form, 'submit');
                        };
                    }
                });

                Vue.directive('header', {
                    bind: function () {
                        var el = this.el,
	                        directive = this;

	                    me.setBinding(directive);

                        app.loadModule(Enpowi.session.theme + '/header', function (html) {
                            el.appendChild(html);
	                        me.doneBinding(directive);
                        });
                    }
                });

                Vue.directive('navigation', {
                    bind: function () {
                        var el = this.el,
	                        directive = this;

	                    me.setBinding(directive);

                        app.loadModule(Enpowi.session.theme + '/navigation', function (html) {
                            el.appendChild(html);
	                        me.doneBinding(directive);
                        });
                    }
                });

                Vue.directive('article', {
                    bind: function () {
	                    var el = this.el;
                        me.defaultModuleElement = el;
                    }
                });

                Vue.directive('side', {
                    bind: function () {
	                    var directive = this;
	                    me.setBinding(directive);
	                    var el = this.el;

	                    app.loadModule(Enpowi.session.theme + '/' + this.expression, function (html) {
		                    el.appendChild(html);
		                    me.doneBinding(directive);
	                    });
                    }
                });

                Vue.directive('footer', {
                    bind: function () {
	                    var directive = this;
	                    me.setBinding(directive);
                        var el = this.el;

                        app.loadModule(Enpowi.session.theme + '/footer', function (html) {
                            el.appendChild(html);
	                        me.doneBinding(directive);
                        });
                    }
                });

                Vue.directive('source-edit', {
                    bind: function() {
                        var directive = this,
	                        el = this.el,
                            form = el.form,
                            styleUrls = ["vendor/codemirror/lib/codemirror.css"],
                            scriptUrls = ["vendor/codemirror/lib/codemirror.js"],
                            mode = '';

	                    me.setBinding(directive);

                        switch (this.expression.toLowerCase()) {
                            case "wikilingo":
                                styleUrls.push("vendor/codemirror.wikilingo/wikiLingo.css");
                                scriptUrls.push("vendor/codemirror.wikilingo/wikiLingo.js");
                                mode = 'text/wikiLingo';
                                break;
                            default:

                        }

                        el.parentNode.appendChild(app.loadStyles(styleUrls));
                        app.loadScripts(scriptUrls, function() {
                            var cm = CodeMirror.fromTextArea(el, {
                                mode: mode
                            });

                            cm.on('change', function() {
                                el.value = cm.getValue();
                            });

                            form.addEventListener('submit', function() {
                                el.value = cm.getValue();
                            });

	                        me.doneBinding(directive);
                        });
                    }
                });

	            Vue.directive('frame', {
		            bind: function() {
			            var el = this.el;
			            app.load(Enpowi.module.url(this.expression), function(html) {
				            $(el).append(html);
			            });
		            }
	            });
            }
        }
    });<commit_msg>Allow frame directive to rewrite it's url if needed<commit_after>Namespace('Enpowi').

    Class('directives', {
        Static: {
            defaultModuleElement: null,
            queue: [],
            data: {},
            app: null,
	        bindingDirectives: [],
	        setBinding: function(directive) {
		        this.bindingDirectives.push(directive);
	        },
	        doneBinding: function(directive) {
		        this.bindingDirectives.splice(this.bindingDirectives.indexOf(directive), 1);

		        if (this.bindingDirectives.length < 1) {
			        Enpowi.App.pub('directive.ready');
		        }
	        },
            setup: function (app) {
                var me = this;
                me.app = app;

                Vue.directive('module', {
                    bind: function () {
                        var el = this.el,
	                        directive = this;
	                    me.setBinding(directive);
                        switch (el.nodeName) {
                            case 'FORM':
                                Enpowi.forms.strategy(el, this.vm, this);
	                            me.doneBinding(directive);
                        }
                    }
                });

                Vue.directive('module-item', {
                    deep: true,
                    bind: function () {
                        var me = this,
	                        el = this.el;

	                    el.onchange = function () {
		                    var form,
			                    parent;

		                    parent = el.parentNode;

		                    while (parent !== null && parent.nodeName !== 'FORM') {
			                    parent = parent.parentNode;

		                    }

		                    if (parent === null) return;

		                    form = parent;

	                        Enpowi.utilities.trigger(form, 'submit');
                        };
                    }
                });

                Vue.directive('header', {
                    bind: function () {
                        var el = this.el,
	                        directive = this;

	                    me.setBinding(directive);

                        app.loadModule(Enpowi.session.theme + '/header', function (html) {
                            el.appendChild(html);
	                        me.doneBinding(directive);
                        });
                    }
                });

                Vue.directive('navigation', {
                    bind: function () {
                        var el = this.el,
	                        directive = this;

	                    me.setBinding(directive);

                        app.loadModule(Enpowi.session.theme + '/navigation', function (html) {
                            el.appendChild(html);
	                        me.doneBinding(directive);
                        });
                    }
                });

                Vue.directive('article', {
                    bind: function () {
	                    var el = this.el;
                        me.defaultModuleElement = el;
                    }
                });

                Vue.directive('side', {
                    bind: function () {
	                    var directive = this;
	                    me.setBinding(directive);
	                    var el = this.el;

	                    app.loadModule(Enpowi.session.theme + '/' + this.expression, function (html) {
		                    el.appendChild(html);
		                    me.doneBinding(directive);
	                    });
                    }
                });

                Vue.directive('footer', {
                    bind: function () {
	                    var directive = this;
	                    me.setBinding(directive);
                        var el = this.el;

                        app.loadModule(Enpowi.session.theme + '/footer', function (html) {
                            el.appendChild(html);
	                        me.doneBinding(directive);
                        });
                    }
                });

                Vue.directive('source-edit', {
                    bind: function() {
                        var directive = this,
	                        el = this.el,
                            form = el.form,
                            styleUrls = ["vendor/codemirror/lib/codemirror.css"],
                            scriptUrls = ["vendor/codemirror/lib/codemirror.js"],
                            mode = '';

	                    me.setBinding(directive);

                        switch (this.expression.toLowerCase()) {
                            case "wikilingo":
                                styleUrls.push("vendor/codemirror.wikilingo/wikiLingo.css");
                                scriptUrls.push("vendor/codemirror.wikilingo/wikiLingo.js");
                                mode = 'text/wikiLingo';
                                break;
                            default:

                        }

                        el.parentNode.appendChild(app.loadStyles(styleUrls));
                        app.loadScripts(scriptUrls, function() {
                            var cm = CodeMirror.fromTextArea(el, {
                                mode: mode
                            });

                            cm.on('change', function() {
                                el.value = cm.getValue();
                            });

                            form.addEventListener('submit', function() {
                                el.value = cm.getValue();
                            });

	                        me.doneBinding(directive);
                        });
                    }
                });

	            Vue.directive('frame', {
		            bind: function() {
			            var el = this.el;

			            app.load(Enpowi.module.url(this.expression), function(html) {
				            $(el).append(html);
			            });
		            },
		            update: function (url) {
			            if (this.el.hasAttribute('data')) {
				            var el = this.el;
				            app.load(Enpowi.module.url(url), function (html) {
					            $(el).append(html);
				            });
			            }
		            }
	            });
            }
        }
    });<|endoftext|>