<commit_before>/*!
Deck JS - deck.menu
Copyright (c) 2011 Caleb Troughton
Dual licensed under the MIT license and GPL license.
https://github.com/imakewebthings/deck.js/blob/master/MIT-license.txt
https://github.com/imakewebthings/deck.js/blob/master/GPL-license.txt
*/

/*
This module adds the methods and key binding to show and hide a menu of all
slides in the deck. The deck menu state is indicated by the presence of a class
on the deck container.
*/
(function($, undefined) {
  var $document = $(document);
  var $html = $('html');
  var rootSlides;

  var populateRootSlidesArray = function() {
    var options = $.deck('getOptions');
    var slideTest = $.map([
      options.classes.before,
      options.classes.previous,
      options.classes.current,
      options.classes.next,
      options.classes.after
    ], function(el, i) {
      return '.' + el;
    }).join(', ');

    rootSlides = [];
    $.each($.deck('getSlides'), function(i, $slide) {
      var $parentSlides = $slide.parentsUntil(
        options.selectors.container,
        slideTest
      );
      if (!$parentSlides.length) {
        rootSlides.push($slide);
      }
    });
  };

  var bindKeyEvents = function() {
    var options = $.deck('getOptions');
    $document.unbind('keydown.deckmenu');
    $document.bind('keydown.deckmenu', function(event) {
      var isMenuKey = event.which === options.keys.menu;
      isMenuKey = isMenuKey || $.inArray(event.which, options.keys.menu) > -1;
      if (isMenuKey && !event.ctrlKey) {
        $.deck('toggleMenu');
        event.preventDefault();
      }
    });
  };

  var bindTouchEvents = function() {
    var $container = $.deck('getContainer');
    var options = $.deck('getOptions');
    var touchEndTime = 0;
    var currentSlide;

    $container.unbind('touchstart.deckmenu');
    $container.bind('touchstart.deckmenu', function() {
      currentSlide = $.deck('getSlide');
    });
    $container.unbind('touchend.deckmenu');
    $container.bind('touchend.deckmenu', function(event) {
      var now = Date.now();
      var isDoubletap = now - touchEndTime < options.touch.doubletapWindow;

      // Ignore this touch event if it caused a nav change (swipe)
      if (currentSlide !== $.deck('getSlide')) {
        return;
      }
      if (isDoubletap) {
        $.deck('toggleMenu');
        event.preventDefault();
      }
      touchEndTime = now;
    });
  };

  var setupMenuSlideSelection = function() {
    var options = $.deck('getOptions');

    $.each($.deck('getSlides'), function(i, $slide) {
      $slide.unbind('click.deckmenu');
      $slide.bind('click.deckmenu', function(event) {
        if (!$.deck('getContainer').hasClass(opts.classes.menu)) {
          return;
        }
        $.deck('go', i);
        $.deck('hideMenu');
        event.stopPropagation();
        event.preventDefault();
      });
    });
  };

  /*
  Extends defaults/options.

  options.classes.menu
    This class is added to the deck container when showing the slide menu.

  options.classes.htmlMenu
    This class is added to the html element when showing the slide menu.

  options.keys.menu
    The numeric keycode used to toggle between showing and hiding the slide
    menu.

  options.touch.doubletapWindow
    Two consecutive touch events within this number of milliseconds will
    be considered a double tap, and will toggle the menu on touch devices.
  */
  $.extend(true, $.deck.defaults, {
    classes: {
      menu: 'deck-menu',
      htmlMenu: 'deck-menu-html'
    },

    keys: {
      menu: 77 // m
    },

    touch: {
      doubletapWindow: 400
    }
  });

  /*
  jQuery.deck('showMenu')

  Shows the slide menu by adding the class specified by the menu class option
  to the deck container.
  */
  $.deck('extend', 'showMenu', function() {
    var $container = $.deck('getContainer');
    var options = $.deck('getOptions');

    if ($container.hasClass(options.classes.menu)) {
      return;
    }

    // Hide through loading class to short-circuit transitions (perf)
    $container.addClass([
      options.classes.loading,
      options.classes.menu
    ].join(' '));
    $html.addClass(options.classes.htmlMenu);

    /* Forced to do this in JS until CSS learns second-grade math. Save old
    style value for restoration when menu is hidden. */
    if (Modernizr.csstransforms) {
      $.each(rootSlides, function(i, $slide) {
        $slide.data('oldStyle', $slide.attr('style'));
        $slide.css({
          'position': 'absolute',
          'left': ((i % 4) * 25) + '%',
          'top': (Math.floor(i / 4) * 25) + '%'
        });
      });
    }

    // Need to ensure the loading class renders first, then remove
    window.setTimeout(function() {
      $container.removeClass(options.classes.loading);
      $container.scrollTop($.deck('getSlide').position().top);
    }, 0);
  });

  /*
  jQuery.deck('hideMenu')

  Hides the slide menu by removing the class specified by the menu class
  option from the deck container.
  */
  $.deck('extend', 'hideMenu', function() {
    var $container = $.deck('getContainer');
    var options = $.deck('getOptions');

    if (!$container.hasClass(options.classes.menu)) {
      return;
    }

    $container.removeClass(options.classes.menu);
    $html.removeClass(options.classes.htmlMenu);
    $container.addClass(options.classes.loading);

    /* Restore old style value */
    if (Modernizr.csstransforms) {
      $.each(rootSlides, function(i, $slide) {
        var oldStyle = $slide.data('oldStyle');
        $slide.attr('style', oldStyle ? oldStyle : '');
      });
    }

    window.setTimeout(function() {
      $container.removeClass(options.classes.loading);
      $container.scrollTop(0);
    }, 0);
  });

  /*
  jQuery.deck('toggleMenu')

  Toggles between showing and hiding the slide menu.
  */
  $.deck('extend', 'toggleMenu', function() {
    $.deck('getContainer').hasClass($.deck('getOptions').classes.menu) ?
    $.deck('hideMenu') : $.deck('showMenu');
  });

  $document.bind('deck.init', function() {
    populateRootSlidesArray();
    bindKeyEvents();
    bindTouchEvents();
    setupMenuSlideSelection();
  });

  $document.bind('deck.change', function(event, from, to) {
    var $container = $.deck('getContainer');
    var containerScroll, slideTop;

    if ($container.hasClass($.deck('getOptions').classes.menu)) {
      containerScroll = $container.scrollTop();
      slideTop = $.deck('getSlide', to).position().top;
      $container.scrollTop(containerScroll + slideTop);
    }
  });
})(jQuery);
<commit_msg>Fix typo from refactoring<commit_after>/*!
Deck JS - deck.menu
Copyright (c) 2011 Caleb Troughton
Dual licensed under the MIT license and GPL license.
https://github.com/imakewebthings/deck.js/blob/master/MIT-license.txt
https://github.com/imakewebthings/deck.js/blob/master/GPL-license.txt
*/

/*
This module adds the methods and key binding to show and hide a menu of all
slides in the deck. The deck menu state is indicated by the presence of a class
on the deck container.
*/
(function($, undefined) {
  var $document = $(document);
  var $html = $('html');
  var rootSlides;

  var populateRootSlidesArray = function() {
    var options = $.deck('getOptions');
    var slideTest = $.map([
      options.classes.before,
      options.classes.previous,
      options.classes.current,
      options.classes.next,
      options.classes.after
    ], function(el, i) {
      return '.' + el;
    }).join(', ');

    rootSlides = [];
    $.each($.deck('getSlides'), function(i, $slide) {
      var $parentSlides = $slide.parentsUntil(
        options.selectors.container,
        slideTest
      );
      if (!$parentSlides.length) {
        rootSlides.push($slide);
      }
    });
  };

  var bindKeyEvents = function() {
    var options = $.deck('getOptions');
    $document.unbind('keydown.deckmenu');
    $document.bind('keydown.deckmenu', function(event) {
      var isMenuKey = event.which === options.keys.menu;
      isMenuKey = isMenuKey || $.inArray(event.which, options.keys.menu) > -1;
      if (isMenuKey && !event.ctrlKey) {
        $.deck('toggleMenu');
        event.preventDefault();
      }
    });
  };

  var bindTouchEvents = function() {
    var $container = $.deck('getContainer');
    var options = $.deck('getOptions');
    var touchEndTime = 0;
    var currentSlide;

    $container.unbind('touchstart.deckmenu');
    $container.bind('touchstart.deckmenu', function() {
      currentSlide = $.deck('getSlide');
    });
    $container.unbind('touchend.deckmenu');
    $container.bind('touchend.deckmenu', function(event) {
      var now = Date.now();
      var isDoubletap = now - touchEndTime < options.touch.doubletapWindow;

      // Ignore this touch event if it caused a nav change (swipe)
      if (currentSlide !== $.deck('getSlide')) {
        return;
      }
      if (isDoubletap) {
        $.deck('toggleMenu');
        event.preventDefault();
      }
      touchEndTime = now;
    });
  };

  var setupMenuSlideSelection = function() {
    var options = $.deck('getOptions');

    $.each($.deck('getSlides'), function(i, $slide) {
      $slide.unbind('click.deckmenu');
      $slide.bind('click.deckmenu', function(event) {
        if (!$.deck('getContainer').hasClass(options.classes.menu)) {
          return;
        }
        $.deck('go', i);
        $.deck('hideMenu');
        event.stopPropagation();
        event.preventDefault();
      });
    });
  };

  /*
  Extends defaults/options.

  options.classes.menu
    This class is added to the deck container when showing the slide menu.

  options.classes.htmlMenu
    This class is added to the html element when showing the slide menu.

  options.keys.menu
    The numeric keycode used to toggle between showing and hiding the slide
    menu.

  options.touch.doubletapWindow
    Two consecutive touch events within this number of milliseconds will
    be considered a double tap, and will toggle the menu on touch devices.
  */
  $.extend(true, $.deck.defaults, {
    classes: {
      menu: 'deck-menu',
      htmlMenu: 'deck-menu-html'
    },

    keys: {
      menu: 77 // m
    },

    touch: {
      doubletapWindow: 400
    }
  });

  /*
  jQuery.deck('showMenu')

  Shows the slide menu by adding the class specified by the menu class option
  to the deck container.
  */
  $.deck('extend', 'showMenu', function() {
    var $container = $.deck('getContainer');
    var options = $.deck('getOptions');

    if ($container.hasClass(options.classes.menu)) {
      return;
    }

    // Hide through loading class to short-circuit transitions (perf)
    $container.addClass([
      options.classes.loading,
      options.classes.menu
    ].join(' '));
    $html.addClass(options.classes.htmlMenu);

    /* Forced to do this in JS until CSS learns second-grade math. Save old
    style value for restoration when menu is hidden. */
    if (Modernizr.csstransforms) {
      $.each(rootSlides, function(i, $slide) {
        $slide.data('oldStyle', $slide.attr('style'));
        $slide.css({
          'position': 'absolute',
          'left': ((i % 4) * 25) + '%',
          'top': (Math.floor(i / 4) * 25) + '%'
        });
      });
    }

    // Need to ensure the loading class renders first, then remove
    window.setTimeout(function() {
      $container.removeClass(options.classes.loading);
      $container.scrollTop($.deck('getSlide').position().top);
    }, 0);
  });

  /*
  jQuery.deck('hideMenu')

  Hides the slide menu by removing the class specified by the menu class
  option from the deck container.
  */
  $.deck('extend', 'hideMenu', function() {
    var $container = $.deck('getContainer');
    var options = $.deck('getOptions');

    if (!$container.hasClass(options.classes.menu)) {
      return;
    }

    $container.removeClass(options.classes.menu);
    $html.removeClass(options.classes.htmlMenu);
    $container.addClass(options.classes.loading);

    /* Restore old style value */
    if (Modernizr.csstransforms) {
      $.each(rootSlides, function(i, $slide) {
        var oldStyle = $slide.data('oldStyle');
        $slide.attr('style', oldStyle ? oldStyle : '');
      });
    }

    window.setTimeout(function() {
      $container.removeClass(options.classes.loading);
      $container.scrollTop(0);
    }, 0);
  });

  /*
  jQuery.deck('toggleMenu')

  Toggles between showing and hiding the slide menu.
  */
  $.deck('extend', 'toggleMenu', function() {
    $.deck('getContainer').hasClass($.deck('getOptions').classes.menu) ?
    $.deck('hideMenu') : $.deck('showMenu');
  });

  $document.bind('deck.init', function() {
    populateRootSlidesArray();
    bindKeyEvents();
    bindTouchEvents();
    setupMenuSlideSelection();
  });

  $document.bind('deck.change', function(event, from, to) {
    var $container = $.deck('getContainer');
    var containerScroll, slideTop;

    if ($container.hasClass($.deck('getOptions').classes.menu)) {
      containerScroll = $container.scrollTop();
      slideTop = $.deck('getSlide', to).position().top;
      $container.scrollTop(containerScroll + slideTop);
    }
  });
})(jQuery);
<|endoftext|>


<commit_before>/**
 * Created by PranavJain on 2/20/17.
 */
var express = require('express');
var router = express.Router();
var mongoose = require('mongoose-q')(require('mongoose'));

var passport = require('../../lib/auth');
var helpers = require('../../lib/helpers');
var User = require('../../models/user');
var Merchant = require('../../models/merchant');
var Product = require('../../models/product.js');
var redis = require('../../lib/redis');
var stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
// using redis, create, edit and delete tabs
//(todo) set up authentication for each merchant to access redis (NO ONE ELSE)

//commands for redis
/*
(create)
 HMSET tab:MerchantID.UserID userID "<UserID>" merchantID "<MerchantID>" tabTotal <total tab money spent> Products "[{productID: \"product3\", name: \"product3\", time: { type: Date, default: Date.now }}]"

    (add to running list) so its searchable...
    SADD tabs:MerchantID MerchantID.UserID

 (get)
 HMGET tab:MerchantID.UserID tabTotal merchantID Products etc.

(search for tabs that are associated with a merchant)
 SSCAN tabs 0 MATCH MerchantID*

 (delete)
  DEL tab:MerchantID.UserID
    (delete from running list) so its no longer searched...
    SREM users "MerchantID.UserID"

    tabs: {
        MerchantID : {
            userID1 : {
                userID: "",
                merchantID: "",
                tabTotal: 0,
                numProducts: 0,
                Products: [
                    {
                        productID: "",
                        name: "",
                        time: "Date.now"
                    },
                    {
                        productID: "",
                        name: "",
                        time: "Date.now"
                    }
                ]
            },
            userID2 : {
                userID: "",
                merchantID: "",
                tabTotal: 0,
                numProducts 0,
                Products: [
                    {
                        productID: "",
                        name: "",
                        time: "Date.now"
                    },
                    {
                        productID: "",
                        name: "",
                        time: "Date.now"
                    }
                ]
            }
        }
    }

 */

//Todo: set up failsafes for all methods!!!!

//authorization functions
const requireAuth = passport.authenticate('user-mobile', { session: false });
//all functions with "requireAuth" used to have helpers.ensureAuthenticated

//open Tab localhost:8080/tabs/open/:id <- id is for Merchant
router.post('/open', requireAuth,
    function(req, res, next) {
        //var store = new Store({
        //    'name': req.body.name,
        //    'description': req.body.description,
        //});
        var userID = req.user._id.toString();
        var merchantID = req.body.id;
        console.log("OPENING TAB WITH USER: " + userID + "\n and MERCHANT: " + merchantID);
        var tabKey = "tab:" + merchantID + "." + userID;

        //todo: if redis.exists(tabkey) then throw danger error.
        //could mean that they are trying to override their currently open tab...
        redis.hmset(tabKey, {
                "userID": userID,
                "merchantID": merchantID,
                "tabTotal": 0.0,
                "numProducts": 0

            }, function(err, reply) {
            // reply is null when the key is missing
                if(err){
                    return next(err);
                }else {
                    console.log("Successfully opened tab!");
                    res.status(200)
                        .json({
                            status: 'success',
                            data: reply,
                            message: 'Retrieved tab.'
                        });
                }
        });

        var tabsMember = merchantID + "." + userID;
        var merchantTabs = "tabs:" + merchantID;
        redis.sadd(merchantTabs, tabsMember, function(err, reply) {
            if(reply){
                console.log("Successfully added tabID to set.");
            }
        });

        //test output
        redis.hgetall(tabKey, function (err, obj) {
            console.dir(obj);
        });
        redis.smembers(merchantTabs, function(err, reply) {
            console.log(reply);
        });

    });

//close tab

//update tab add and remove product

/*
 tabs.MerchantID.userID1.Products.0.productID: ""
 tabs.MerchantID.userID1.Products.0.time: "Date.now"
 tabs.MerchantID.userID1.Products.1.productID: ""
 tabs.MerchantID.userID1.Products.1.time: "Date.now"
 */
//add item to tab
router.post('/update/add', helpers.ensureMerchantAuthenticated,
    function(req, res, next) {
        //var store = new Store({
        //    'name': req.body.name,
        //    'description': req.body.description,
        //});
        var merchantID = req.user._id.toString();
        var userID = req.body.id;
        var productID = req.body.productID;

        console.log("ADDING PRODUCT: " + productID + "\n TO TAB WITH USER: " + userID + "\n and MERCHANT: " + merchantID);
        var tabKey = "tab:" + merchantID + "." + userID;

        redis.hget(tabKey, "numProducts", function (err, reply) {
            if(err){
                console.log(err);
            }
            var numProducts = parseInt(reply.toString());
            var productIDKey = "Products." + numProducts + ".productID";
            var productTimeKey = "Products." + numProducts + ".time";
            var cost = 0.0;
            var obj = {};
            var date = new Date();
            obj[productIDKey] = productID;
            obj[productTimeKey] = date.toISOString();
            //figure how to use q to do this better...
            Product.findById(productID, function(err, product) {
                if (err) {
                    return next(err);
                } else {
                    cost = product.amount;
                    //all the redis commands need to go into here.
                    redis.multi().
                    hincrby(tabKey, "numProducts", 1).
                    HINCRBYFLOAT(tabKey, "tabTotal", cost).
                    hmset(tabKey, obj).exec(
                        function(err, reply) {
                            // reply is null when the key is missing
                            if(err){
                                return next(err);
                            }else{
                                console.log("Successfully added to tab!");
                                //test output
                                redis.hgetall(tabKey, function (err, obj) {
                                    console.dir(obj);
                                    res.status(200)
                                        .json({
                                            status: 'success',
                                            data: obj,
                                            message: 'Retrieved tab.'
                                        });
                                });
                            }
                        });
                }
            });

        });

    });

//remove item from tab
router.post('/update/remove', helpers.ensureMerchantAuthenticated,
    function(req, res, next) {

        var merchantID = req.user._id.toString();
        var userID = req.body.id;

        //this is the index number of the item we would like to remove from the tab
        var itemNum = req.body.itemNum;

        console.log("Removing PRODUCT in index: " + itemNum + "\n IN TAB WITH USER: " + userID + "\n and MERCHANT: " + merchantID);

        var tabKey = "tab:" + merchantID + "." + userID;
        var productIDKey = "Products." + itemNum + ".productID";
        var productTimeKey = "Products." + itemNum + ".time";
        redis.hget(tabKey, productIDKey, function (err, reply) {
            var productID = reply;
            var cost = 0.0;
            //figure how to use q to do this better...
            Product.findById(productID, function(err, product) {
                if (err) {
                    return next(err);
                } else {
                    cost = -1*product.amount;
                    //decrement numproducts and tab total. get get rid of it from list.
                    redis.multi().
                    hincrby(tabKey, "numProducts", -1).
                    HINCRBYFLOAT(tabKey, "tabTotal", cost).
                    hdel(tabKey, productIDKey, productTimeKey)
                        .exec(function(err, reply) {
                            // reply is null when the key is missing
                            if(err){
                                return next(err);
                            }else {
                                console.log("Successfully removed from tab!");
                                //test output
                                redis.hgetall(tabKey, function (err, obj) {
                                    console.dir(obj);
                                    res.status(200)
                                        .json({
                                            status: 'success',
                                            data: obj,
                                            message: 'Retrieved tab.'
                                        });
                                });
                            }
                        });
                }
            });

        });

    });

/*
keys   for index 0
 Products.0.productID = ProductID
 Products.0.time = Date.now
 */

//get all tabs w/ merchantID
//what information should we return?
router.post('/getall', helpers.ensureMerchantAuthenticated,
    function(req, res, next) {

        var merchantID = req.user._id.toString();

        //this is the index number of the item we would like to remove from the tab

        console.log("Getting all tabs for MERCHANT: " + merchantID);

        var tabsKey = "tabs:" + merchantID;
        redis.smembers(tabsKey, function (err, reply) {
            if(err){
                return next(err);
            }
            else {
                var allTabs = reply;
                console.log("All tabs: " + allTabs);
                res.status(200)
                    .json({
                        status: 'success',
                        data: allTabs,
                        message: 'Retrieved all tabs from merchant.'
                    });
            }
        });

    });

//get tab info for user w/ userID like tabTotal and products bought
//passing in merchantID for this specific user
router.get('/tab/:id', requireAuth,
    function(req, res, next) {

        var userID = req.user._id.toString();
        var merchantID = req.params.id;
        //this is the index number of the item we would like to remove from the tab

        var tabKey = "tab:" + merchantID + "." + userID;
        //test output
        redis.hgetall(tabKey, function (err, obj) {
            if(err){
                return next(err);
            }else {
                console.dir(obj);
                res.status(200)
                    .json({
                        status: 'success',
                        data: obj,
                        message: 'Retrieved tab.'
                    });
            }
        });

    });

//get User for merchant to populate information on bartender dashboard
//passing in userID
router.get('/user/:id', helpers.ensureMerchantAuthenticated,
    function(req, res, next) {

        var merchantID = req.user._id.toString();
        var userID = req.params.id;
        //this is the index number of the item we would like to remove from the tab

        var tabKey = "tab:" + merchantID + "." + userID;
        //test output
        redis.hgetall(tabKey, function (err, obj) {
            if(err){
                return next(err);
            }else {
                console.dir(obj);
                res.status(200)
                    .json({
                        status: 'success',
                        data: obj,
                        message: 'Retrieved tab.'
                    });
            }
        });

    });

//(todo) when exiting or deleting tab, make it go through charge paths to charge card
//close tab
router.post('/close', requireAuth,
    function(req, res, next) {
        //var store = new Store({
        //    'name': req.body.name,
        //    'description': req.body.description,
        //});
        var userID = req.user._id.toString();
        var merchantID = req.body.id;
        console.log("CLOSING TAB WITH USER: " + userID + "\n and MERCHANT: " + merchantID);

        var tabKey = "tab:" + merchantID + "." + userID;

        redis.hgetall(tabKey, function (err, tab) {
            if(err || !tab){
                return next(err);
            }else {
                console.dir(tab);
                var cost = Number(tab.tabTotal); //total payment required
                //todo: get currency to work with different types
                var numProducts = parseInt(tab.numProducts);
                //get all products in array
                var productIds = [];
                var productTimes = [];
                for(var i=0; i < numProducts; i++){
                    var productIDKey = "Products." + i + ".productID";
                    var productTimeKey = "Products." + i + ".time";
                    console.log("For Key: " + productIDKey + ", we got: " + tab[productIDKey]);
                    productIds.push(tab[productIDKey]);
                    productTimes.push(new Date(tab[productTimeKey]));
                }
                console.log("Arrays look like: " + productIds.toString() + " and " + productTimes.toString());
                //let charge go through
                User.findById(userID, function(err, user) {
                    if (err) {
                        return next(err);
                    } else {
                        //get rid of this cuz no need for users to have this data
                        user.products.push({ productID: req.body.productID, name: "allDaProducts", token: "PreviouSource" });
                        user.save();

                        // Create Charge
                        var charge = {
                            amount: cost*100.0,
                            currency: 'USD',
                            customer: user.stripe,
                            description: "Tab bought with stored card number"
                        };
                        stripe.charges.create(charge, function(err, charge) {
                            if(err) {
                                return next(err);
                            } else {
                                req.flash('message', {
                                    status: 'success',
                                    value: 'Thanks for coming!'
                                });
                                console.log("Successfully made a purchase with stored card on a TAB!");
                                res.redirect('/auth/profile');
                                //will stuff still happen?
                                //get and push new Product data to merchant...
                                console.log("attempting sales input");
                                Product.find({ '_id': { $in: productIds }}, { name: 1, amount: 1 }, function(err, data) {
                                    if(!err) {
                                        console.log(data);
                                        //now put it into merchant
                                        var productData = [];
                                        for (var i = 0; i < numProducts; i++) {
                                            //find repeats... see if you can get rid of this so it runs faster than O(n*m) time...
                                            //could get rid of it if mongo auto returns json instead of array...
                                            var productName = "";
                                            var productAmount = 0.0;
                                            for(var j = 0; j < data.length; j++){
                                                if(productIds[i] == data[j]._id){
                                                    productName = data[j].name;
                                                    productAmount = data[j].amount;
                                                }
                                            }
                                            var obj = {
                                                productID: productIds[i],
                                                name: productName,
                                                token: "Tabs4Life",
                                                price: productAmount,
                                                time: productTimes[i]
                                            };
                                            productData.push(obj);
                                        }
                                        var options = {new: true};
                                        Merchant.findByIdAndUpdate(merchantID, {$push: {sales: {$each: productData}}}, options, function (err, merchant) {
                                            if (err) {
                                                console.log(err);
                                            }
                                            console.log("Updated Merchant: " + merchant);
                                        });
                                    }else{
                                        console.log(err);
                                    }
                                });
                                //delete tab from redis
                                var tabsMember = merchantID + "." + userID;
                                var merchantTabs = "tabs:" + merchantID;
                                redis.multi().
                                del(tabKey).
                                srem(merchantTabs, tabsMember).
                                exec(function (err, reply) {
                                    if(err){
                                        console.log(err);
                                    }else {
                                        console.log("Deleted Key from redis!");

                                    }
                                });

                            }
                        });

                    }
                });
            }
        });

    });

module.exports = router;
<commit_msg>Fixed issue where charge amount may have not been an integer.<commit_after>/**
 * Created by PranavJain on 2/20/17.
 */
var express = require('express');
var router = express.Router();
var mongoose = require('mongoose-q')(require('mongoose'));

var passport = require('../../lib/auth');
var helpers = require('../../lib/helpers');
var User = require('../../models/user');
var Merchant = require('../../models/merchant');
var Product = require('../../models/product.js');
var redis = require('../../lib/redis');
var stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
// using redis, create, edit and delete tabs
//(todo) set up authentication for each merchant to access redis (NO ONE ELSE)

//commands for redis
/*
(create)
 HMSET tab:MerchantID.UserID userID "<UserID>" merchantID "<MerchantID>" tabTotal <total tab money spent> Products "[{productID: \"product3\", name: \"product3\", time: { type: Date, default: Date.now }}]"

    (add to running list) so its searchable...
    SADD tabs:MerchantID MerchantID.UserID

 (get)
 HMGET tab:MerchantID.UserID tabTotal merchantID Products etc.

(search for tabs that are associated with a merchant)
 SSCAN tabs 0 MATCH MerchantID*

 (delete)
  DEL tab:MerchantID.UserID
    (delete from running list) so its no longer searched...
    SREM users "MerchantID.UserID"

    tabs: {
        MerchantID : {
            userID1 : {
                userID: "",
                merchantID: "",
                tabTotal: 0,
                numProducts: 0,
                Products: [
                    {
                        productID: "",
                        name: "",
                        time: "Date.now"
                    },
                    {
                        productID: "",
                        name: "",
                        time: "Date.now"
                    }
                ]
            },
            userID2 : {
                userID: "",
                merchantID: "",
                tabTotal: 0,
                numProducts 0,
                Products: [
                    {
                        productID: "",
                        name: "",
                        time: "Date.now"
                    },
                    {
                        productID: "",
                        name: "",
                        time: "Date.now"
                    }
                ]
            }
        }
    }

 */

//Todo: set up failsafes for all methods!!!!

//authorization functions
const requireAuth = passport.authenticate('user-mobile', { session: false });
//all functions with "requireAuth" used to have helpers.ensureAuthenticated

//open Tab localhost:3000/tabs/open/:id <- id is for Merchant
router.post('/open', requireAuth,
    function(req, res, next) {
        //var store = new Store({
        //    'name': req.body.name,
        //    'description': req.body.description,
        //});
        var userID = req.user._id.toString();
        var merchantID = req.body.id;
        console.log("OPENING TAB WITH USER: " + userID + "\n and MERCHANT: " + merchantID);
        var tabKey = "tab:" + merchantID + "." + userID;

        //todo: if redis.exists(tabkey) then throw danger error.
        //could mean that they are trying to override their currently open tab...
        redis.hmset(tabKey, {
                "userID": userID,
                "merchantID": merchantID,
                "tabTotal": 0.0,
                "numProducts": 0

            }, function(err, reply) {
            // reply is null when the key is missing
                if(err){
                    return next(err);
                }else {
                    console.log("Successfully opened tab!");
                    res.status(200)
                        .json({
                            status: 'success',
                            data: reply,
                            message: 'Retrieved tab.'
                        });
                }
        });

        var tabsMember = merchantID + "." + userID;
        var merchantTabs = "tabs:" + merchantID;
        redis.sadd(merchantTabs, tabsMember, function(err, reply) {
            if(reply){
                console.log("Successfully added tabID to set.");
            }
        });

        //test output
        redis.hgetall(tabKey, function (err, obj) {
            console.dir(obj);
        });
        redis.smembers(merchantTabs, function(err, reply) {
            console.log(reply);
        });

    });

//close tab

//update tab add and remove product

/*
 tabs.MerchantID.userID1.Products.0.productID: ""
 tabs.MerchantID.userID1.Products.0.time: "Date.now"
 tabs.MerchantID.userID1.Products.1.productID: ""
 tabs.MerchantID.userID1.Products.1.time: "Date.now"
 */
//add item to tab
router.post('/update/add', helpers.ensureMerchantAuthenticated,
    function(req, res, next) {
        //var store = new Store({
        //    'name': req.body.name,
        //    'description': req.body.description,
        //});
        var merchantID = req.user._id.toString();
        var userID = req.body.id;
        var productID = req.body.productID;

        console.log("ADDING PRODUCT: " + productID + "\n TO TAB WITH USER: " + userID + "\n and MERCHANT: " + merchantID);
        var tabKey = "tab:" + merchantID + "." + userID;

        redis.hget(tabKey, "numProducts", function (err, reply) {
            if(err){
                console.log(err);
            }
            var numProducts = parseInt(reply.toString());
            var productIDKey = "Products." + numProducts + ".productID";
            var productTimeKey = "Products." + numProducts + ".time";
            var cost = 0.0;
            var obj = {};
            var date = new Date();
            obj[productIDKey] = productID;
            obj[productTimeKey] = date.toISOString();
            //figure how to use q to do this better...
            Product.findById(productID, function(err, product) {
                if (err) {
                    return next(err);
                } else {
                    cost = product.amount;
                    //all the redis commands need to go into here.
                    redis.multi().
                    hincrby(tabKey, "numProducts", 1).
                    HINCRBYFLOAT(tabKey, "tabTotal", cost).
                    hmset(tabKey, obj).exec(
                        function(err, reply) {
                            // reply is null when the key is missing
                            if(err){
                                return next(err);
                            }else{
                                console.log("Successfully added to tab!");
                                //test output
                                redis.hgetall(tabKey, function (err, obj) {
                                    console.dir(obj);
                                    res.status(200)
                                        .json({
                                            status: 'success',
                                            data: obj,
                                            message: 'Retrieved tab.'
                                        });
                                });
                            }
                        });
                }
            });

        });

    });

//remove item from tab
router.post('/update/remove', helpers.ensureMerchantAuthenticated,
    function(req, res, next) {

        var merchantID = req.user._id.toString();
        var userID = req.body.id;

        //this is the index number of the item we would like to remove from the tab
        var itemNum = req.body.itemNum;

        console.log("Removing PRODUCT in index: " + itemNum + "\n IN TAB WITH USER: " + userID + "\n and MERCHANT: " + merchantID);

        var tabKey = "tab:" + merchantID + "." + userID;
        var productIDKey = "Products." + itemNum + ".productID";
        var productTimeKey = "Products." + itemNum + ".time";
        redis.hget(tabKey, productIDKey, function (err, reply) {
            var productID = reply;
            var cost = 0.0;
            //figure how to use q to do this better...
            Product.findById(productID, function(err, product) {
                if (err) {
                    return next(err);
                } else {
                    cost = -1*product.amount;
                    //decrement numproducts and tab total. get get rid of it from list.
                    redis.multi().
                    hincrby(tabKey, "numProducts", -1).
                    HINCRBYFLOAT(tabKey, "tabTotal", cost).
                    hdel(tabKey, productIDKey, productTimeKey)
                        .exec(function(err, reply) {
                            // reply is null when the key is missing
                            if(err){
                                return next(err);
                            }else {
                                console.log("Successfully removed from tab!");
                                //test output
                                redis.hgetall(tabKey, function (err, obj) {
                                    console.dir(obj);
                                    res.status(200)
                                        .json({
                                            status: 'success',
                                            data: obj,
                                            message: 'Retrieved tab.'
                                        });
                                });
                            }
                        });
                }
            });

        });

    });

/*
keys   for index 0
 Products.0.productID = ProductID
 Products.0.time = Date.now
 */

//get all tabs w/ merchantID
//what information should we return?
router.post('/getall', helpers.ensureMerchantAuthenticated,
    function(req, res, next) {

        var merchantID = req.user._id.toString();

        //this is the index number of the item we would like to remove from the tab

        console.log("Getting all tabs for MERCHANT: " + merchantID);

        var tabsKey = "tabs:" + merchantID;
        redis.smembers(tabsKey, function (err, reply) {
            if(err){
                return next(err);
            }
            else {
                var allTabs = reply;
                console.log("All tabs: " + allTabs);
                res.status(200)
                    .json({
                        status: 'success',
                        data: allTabs,
                        message: 'Retrieved all tabs from merchant.'
                    });
            }
        });

    });

//get tab info for user w/ userID like tabTotal and products bought
//passing in merchantID for this specific user
router.get('/tab/:id', requireAuth,
    function(req, res, next) {

        var userID = req.user._id.toString();
        var merchantID = req.params.id;
        //this is the index number of the item we would like to remove from the tab

        var tabKey = "tab:" + merchantID + "." + userID;
        //test output
        redis.hgetall(tabKey, function (err, obj) {
            if(err){
                return next(err);
            }else {
                console.dir(obj);
                res.status(200)
                    .json({
                        status: 'success',
                        data: obj,
                        message: 'Retrieved tab.'
                    });
            }
        });

    });

//get User for merchant to populate information on bartender dashboard
//passing in userID
router.get('/user/:id', helpers.ensureMerchantAuthenticated,
    function(req, res, next) {

        var merchantID = req.user._id.toString();
        var userID = req.params.id;
        //this is the index number of the item we would like to remove from the tab

        var tabKey = "tab:" + merchantID + "." + userID;
        //test output
        redis.hgetall(tabKey, function (err, obj) {
            if(err){
                return next(err);
            }else {
                console.dir(obj);
                res.status(200)
                    .json({
                        status: 'success',
                        data: obj,
                        message: 'Retrieved tab.'
                    });
            }
        });

    });

//(todo) when exiting or deleting tab, make it go through charge paths to charge card
//todo: allow function to close tabs even when there is no bill i.e tabTotal = 0
//close tab
router.post('/close', requireAuth,
    function(req, res, next) {
        //var store = new Store({
        //    'name': req.body.name,
        //    'description': req.body.description,
        //});
        var userID = req.user._id.toString();
        var merchantID = req.body.id;
        console.log("CLOSING TAB WITH USER: " + userID + "\n and MERCHANT: " + merchantID);

        var tabKey = "tab:" + merchantID + "." + userID;

        redis.hgetall(tabKey, function (err, tab) {
            if(err || !tab){
                return next(err);
            }else {
                console.dir(tab);
                var cost = Number(tab.tabTotal); //total payment required
                //todo: get currency to work with different types
                var numProducts = parseInt(tab.numProducts);
                //get all products in array
                var productIds = [];
                var productTimes = [];
                for(var i=0; i < numProducts; i++){
                    var productIDKey = "Products." + i + ".productID";
                    var productTimeKey = "Products." + i + ".time";
                    console.log("For Key: " + productIDKey + ", we got: " + tab[productIDKey]);
                    productIds.push(tab[productIDKey]);
                    productTimes.push(new Date(tab[productTimeKey]));
                }
                console.log("Arrays look like: " + productIds.toString() + " and " + productTimes.toString());
                //let charge go through
                User.findById(userID, function(err, user) {
                    if (err) {
                        return next(err);
                    } else {
                        //get rid of this cuz no need for users to have this data
                        user.products.push({ productID: req.body.productID, name: "allDaProducts", token: "PreviouSource" });
                        user.save();

                        // Create Charge
                        var charge = {
                            amount: Math.round(cost*100.0),
                            currency: 'USD',
                            customer: user.stripe,
                            description: "Tab bought with stored card number"
                        };
                        stripe.charges.create(charge, function(err, charge) {
                            if(err) {
                                return next(err);
                            } else {
                                res.status(200)
                                    .json({
                                        status: 'success',
                                        data: charge,
                                        message: 'Retrieved tab.'
                                    });
                                console.log("Successfully made a purchase with stored card on a TAB!");
                                //will stuff still happen?
                                //get and push new Product data to merchant...
                                console.log("attempting sales input");
                                Product.find({ '_id': { $in: productIds }}, { name: 1, amount: 1 }, function(err, data) {
                                    if(!err) {
                                        console.log(data);
                                        //now put it into merchant
                                        var productData = [];
                                        for (var i = 0; i < numProducts; i++) {
                                            //find repeats... see if you can get rid of this so it runs faster than O(n*m) time...
                                            //could get rid of it if mongo auto returns json instead of array...
                                            var productName = "";
                                            var productAmount = 0.0;
                                            for(var j = 0; j < data.length; j++){
                                                if(productIds[i] == data[j]._id){
                                                    productName = data[j].name;
                                                    productAmount = data[j].amount;
                                                }
                                            }
                                            var obj = {
                                                productID: productIds[i],
                                                name: productName,
                                                token: "Tabs4Life",
                                                price: productAmount,
                                                time: productTimes[i]
                                            };
                                            productData.push(obj);
                                        }
                                        var options = {new: true};
                                        Merchant.findByIdAndUpdate(merchantID, {$push: {sales: {$each: productData}}}, options, function (err, merchant) {
                                            if (err) {
                                                console.log(err);
                                            }
                                            console.log("Updated Merchant: " + merchant);
                                        });
                                    }else{
                                        console.log(err);
                                    }
                                });
                                //delete tab from redis
                                var tabsMember = merchantID + "." + userID;
                                var merchantTabs = "tabs:" + merchantID;
                                redis.multi().
                                del(tabKey).
                                srem(merchantTabs, tabsMember).
                                exec(function (err, reply) {
                                    if(err){
                                        console.log(err);
                                    }else {
                                        console.log("Deleted Key from redis!");

                                    }
                                });

                            }
                        });

                    }
                });
            }
        });

    });

module.exports = router;
<|endoftext|>


<commit_before>openerp.project = function(openerp) {
    openerp.web_kanban.ProjectKanban = openerp.web_kanban.KanbanRecord.include({
        bind_events: function() {
            self = this;
            if(this.view.dataset.model == 'project.project') {
                $('.oe_project_kanban_vignette').mouseover(function() {
                    return $(this).find('.oe_project_kanban_action').show();
                    }).mouseout(function() {
                    return $(this).find('.oe_project_kanban_action').hide();
                });
                $('.dropdown-toggle').dropdown();
            }
            self._super();
        }
    });
}
<commit_msg>[IMP]<commit_after>openerp.project = function(openerp) {
    openerp.web_kanban.ProjectKanban = openerp.web_kanban.KanbanRecord.include({
        bind_events: function() {
            $('.oe_project_kanban_vignette').mouseover(function() {
                return $(this).find('.oe_project_kanban_action').show();
                }).mouseout(function() {
                return $(this).find('.oe_project_kanban_action').hide();
            });
            $('.dropdown-toggle').dropdown();
            this._super();
        }
    });
}
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react'
import { IndexLink, Link } from 'react-router'

import FeedbackPanel from './feedbackPanel'

import styles from '../styles/core.scss'
import classNames from 'classnames/bind'

let cx = classNames.bind(styles)

// Note: Stateless/function components *will not* hot reload!
// react-transform *only* works on component classes.
//
// Since layouts rarely change, they are a good place to
// leverage React's new Statelesss Functions:
// https://facebook.github.io/react/docs/reusable-components.html#stateless-functions
//
// App is a pure function of it's props, so we can
// define it with a plain javascript function...
// export default function App({ children, state }) {
export default function App({ location, children, store }) {
	// console.log('this.props: ', this.props)
	// console.log('this.context: ', this.context)
	// let { children, state } = this.props

	// console.log('app.location: ', location)

	let { state, dispatch, actions } = store

	let alert = null
	if ( state.feedback.length !== 0) {
		alert = (

					<FeedbackPanel {...store} />

		)
	}

	let progress = null
	if (state.opInProgress) {
		progress = (

		)
	}

	const stateOk = state.unraid && state.unraid.condition.state === "STARTED"
	const calcDisabled = state.opInProgress || (!stateOk)

	// STATUS:
	const labelStyle = cx({
		'spacer': true,
		'label': true,
		'label-success': stateOk,
		'label-alert': !stateOk,
	})

	let status = null
	let buttons = null
	if (location.pathname === '/' && state.unraid) {
		status = (

				{state.unraid.condition.state}

		)

		buttons = (

				<button className={cx('btn', 'btn-primary')} onClick={calculate.bind(null, actions, dispatch)} disabled={calcDisabled}>CALCULATE</button>
				<button className={cx('btn', 'btn-primary', 'lspacer')} onClick={move.bind(null, actions, dispatch)} disabled={state.moveDisabled || state.opInProgress}>MOVE</button>
				  |

					<input type="checkbox" checked={state.config.dryRun} onChange={toggleDryRun.bind(null, actions, dispatch)} />

					dry run

		)
	}

	let version = state.config ? state.config.version : null

	// var url = require("file!./file.png");
	let unbalance = require("../img/unbalance-logo.png")
	let diskmv = require("../img/diskmv.png")
	let unraid = require('../img/unraid.png')
	let logo = require('../img/logo-small.png')
	let vm = require('../img/v.png')

	let indexActive = cx({
		'lspacer': true,
		'active': true
	})

	let active = cx({
		'active': true
	})

	return (

								<IndexLink to="/" className={cx('lspacer')} activeClassName={indexActive}>HOME</IndexLink>
								|
								<Link to="settings" activeClassName={active}>SETTINGS</Link>

											{ status}

											{ progress }

											{ buttons }

			{ alert }

			{ children }

						Copyright ©
						Juan B. Rodriguez

					unBALANCE v{version}

	)
}

function calculate(actions, dispatch, e) {
	dispatch(actions.calculate)
}

function move(actions, dispatch, e) {
	dispatch(actions.move)
}

function toggleDryRun(actions, dispatch, e) {
	dispatch(actions.toggleDryRun)
}<commit_msg>(front) Disable dry run checkbox when an operation is running<commit_after>import React, { Component, PropTypes } from 'react'
import { IndexLink, Link } from 'react-router'

import FeedbackPanel from './feedbackPanel'

import styles from '../styles/core.scss'
import classNames from 'classnames/bind'

let cx = classNames.bind(styles)

// Note: Stateless/function components *will not* hot reload!
// react-transform *only* works on component classes.
//
// Since layouts rarely change, they are a good place to
// leverage React's new Statelesss Functions:
// https://facebook.github.io/react/docs/reusable-components.html#stateless-functions
//
// App is a pure function of it's props, so we can
// define it with a plain javascript function...
// export default function App({ children, state }) {
export default function App({ location, children, store }) {
	// console.log('this.props: ', this.props)
	// console.log('this.context: ', this.context)
	// let { children, state } = this.props

	// console.log('app.location: ', location)

	let { state, dispatch, actions } = store

	let alert = null
	if ( state.feedback.length !== 0) {
		alert = (

					<FeedbackPanel {...store} />

		)
	}

	let progress = null
	if (state.opInProgress) {
		progress = (

		)
	}

	const stateOk = state.unraid && state.unraid.condition.state === "STARTED"
	const disabled = state.opInProgress || (!stateOk)

	// STATUS:
	const labelStyle = cx({
		'spacer': true,
		'label': true,
		'label-success': stateOk,
		'label-alert': !stateOk,
	})

	let status = null
	let buttons = null
	if (location.pathname === '/' && state.unraid) {
		status = (

				{state.unraid.condition.state}

		)

		buttons = (

				<button className={cx('btn', 'btn-primary')} onClick={calculate.bind(null, actions, dispatch)} disabled={disabled}>CALCULATE</button>
				<button className={cx('btn', 'btn-primary', 'lspacer')} onClick={move.bind(null, actions, dispatch)} disabled={state.moveDisabled || state.opInProgress}>MOVE</button>
				  |

					<input type="checkbox" checked={state.config.dryRun} onChange={toggleDryRun.bind(null, actions, dispatch)} disabled={disabled} />

					dry run

		)
	}

	let version = state.config ? state.config.version : null

	// var url = require("file!./file.png");
	let unbalance = require("../img/unbalance-logo.png")
	let diskmv = require("../img/diskmv.png")
	let unraid = require('../img/unraid.png')
	let logo = require('../img/logo-small.png')
	let vm = require('../img/v.png')

	let indexActive = cx({
		'lspacer': true,
		'active': true
	})

	let active = cx({
		'active': true
	})

	return (

								<IndexLink to="/" className={cx('lspacer')} activeClassName={indexActive}>HOME</IndexLink>
								|
								<Link to="settings" activeClassName={active}>SETTINGS</Link>

											{ status}

											{ progress }

											{ buttons }

			{ alert }

			{ children }

						Copyright ©
						Juan B. Rodriguez

					unBALANCE v{version}

	)
}

function calculate(actions, dispatch, e) {
	dispatch(actions.calculate)
}

function move(actions, dispatch, e) {
	dispatch(actions.move)
}

function toggleDryRun(actions, dispatch, e) {
	dispatch(actions.toggleDryRun)
}<|endoftext|>


<commit_before>fdom.apis.set("core", {
  'createChannel': {type: "method", value: []},
  'bindChannel': {type: "method", value: ["proxy"]}
});

fdom.apis.set("core.view", {
  'open': {type: "method", value: [{
    'file':"string",
    'code':"string"
  }]},
  'show': {type: "method", value: []},
  'close': {type: "method", value: []},
  'postMessage': {type: "method", value: ["object"]},

  'message': {type: "event", value: "object"},
  'onClose': {type: "event", value: []}
});

fdom.apis.set("core.storage", {
  'set': {type: "method", value: ["string", "string"]},
  'get': {type: "method", value: ["string"]},
  'remove': {type: "method", value: ["string"]},
  'clear': {type: "method", value: []},
  'change': {type: "event", value: ["string"]}
});

fdom.apis.set("core.socket", {
  'create': {type: "method", value: ["string", "object"]},
  'connect': {type: "method", value: ["number", "string", "number"]},
  'onData': {type: "event", value: {"socketId": "number", "data": "buffer"}},
  'write': {type: "method", value: ["number", "buffer"]},
  'disconnect': {type: "method", value: ["number"]},
  'destroy': {type: "method", value: ["number"]},
  'listen': {type: "method", value: ["number", "string", "number"]},
  'onConnection': {type: "event", value: {
    "serverSocketId": "number",
    "clientSocketId": "number"}},
  'getInfo': {type: "method", value: ["number"]}
});

fdom.apis.set("core.runtime", {
  'createApp': {type: "method", value: ["string", "proxy"]},
});

fdom.apis.set("core.peerconnection", {
  'open': {type: "method", value: ["proxy"]},
  'postMessage': {type: "method", value: [{"tag": "string", "text": "string", "binary": "blob", "buffer": "buffer"}]},
  'message': {type: "event", value: {"tag": "string", "text": "string", "binary": "blob", "buffer": "buffer"}},

  'close': {type: "method", value: []},
  'onClose': {type: "event", value: []}
});

fdom.apis.set('core.sctp-peerconnection', {
  // Setup the link to the peer and options for this peer connection.
  'setup': {type: "method",
    value: [
      // The 'proxy' object is a freedom channel identifier used to send/receive
      // messages to/from a signalling chanel. TODO: The string is meant to be a
      // boolean, but Freedom doesn't support it yet.
      "proxy",
      // the peerName, used debugging and console messages.
      "string",
      ]
  },

  // Send a message to the peer.
  'send': {type: "method", value: [{
    // Data channel id. If provided, will be used as the channel label.
    // If the channel label doesn't already exist, a new channel will be
    // created.
    "channelLabel": "string",
    // One of the bellow should be defined; this is the data to send.
    "text": "string",
    "binary": "blob",
    "buffer": "buffer"
  }]},

  // Called when we get a message from the peer.
  'onTest': {type: "event", value: [{ "foo": "string" }]},

  // Called when we get a message from the peer.
  'onReceived': {type: "event", value: [{
    // The label/id of the data channel.
    "channelLabel": "string",
    // One the below will be specified.
    "text": "string",
    "binary": "blob",
    "buffer": "buffer"
  }]},

  // Open the data channel with this label.
  'openDataChannel': {type: "method", value: ["string"]},
  // Close the data channel with this label.
  'closeDataChannel': {type: "method", value: ["string"]},

  // A channel with this id has been opened.
  'onOpenDataChannel': {type: "event", value: ["string"]},
  // The channale with this id has been closed.
  'onCloseDataChannel': {type: "event", value: ["string"]},

  // Close the peer connection.
  'close': {type: "method", value: []},
  // The peer connection has been closed.
  'onClose': {type: "event", value: []},
});

<commit_msg>fixed type spec for events<commit_after>fdom.apis.set("core", {
  'createChannel': {type: "method", value: []},
  'bindChannel': {type: "method", value: ["proxy"]}
});

fdom.apis.set("core.view", {
  'open': {type: "method", value: [{
    'file':"string",
    'code':"string"
  }]},
  'show': {type: "method", value: []},
  'close': {type: "method", value: []},
  'postMessage': {type: "method", value: ["object"]},

  'message': {type: "event", value: "object"},
  'onClose': {type: "event", value: []}
});

fdom.apis.set("core.storage", {
  'set': {type: "method", value: ["string", "string"]},
  'get': {type: "method", value: ["string"]},
  'remove': {type: "method", value: ["string"]},
  'clear': {type: "method", value: []},
  'change': {type: "event", value: ["string"]}
});

fdom.apis.set("core.socket", {
  'create': {type: "method", value: ["string", "object"]},
  'connect': {type: "method", value: ["number", "string", "number"]},
  'onData': {type: "event", value: {"socketId": "number", "data": "buffer"}},
  'write': {type: "method", value: ["number", "buffer"]},
  'disconnect': {type: "method", value: ["number"]},
  'destroy': {type: "method", value: ["number"]},
  'listen': {type: "method", value: ["number", "string", "number"]},
  'onConnection': {type: "event", value: {
    "serverSocketId": "number",
    "clientSocketId": "number"}},
  'getInfo': {type: "method", value: ["number"]}
});

fdom.apis.set("core.runtime", {
  'createApp': {type: "method", value: ["string", "proxy"]},
});

fdom.apis.set("core.peerconnection", {
  'open': {type: "method", value: ["proxy"]},
  'postMessage': {type: "method", value: [{"tag": "string", "text": "string", "binary": "blob", "buffer": "buffer"}]},
  'message': {type: "event", value: {"tag": "string", "text": "string", "binary": "blob", "buffer": "buffer"}},

  'close': {type: "method", value: []},
  'onClose': {type: "event", value: {}}
});

fdom.apis.set('core.sctp-peerconnection', {
  // Setup the link to the peer and options for this peer connection.
  'setup': {type: "method",
    value: [
      // The 'proxy' object is a freedom channel identifier used to send/receive
      // messages to/from a signalling chanel. TODO: The string is meant to be a
      // boolean, but Freedom doesn't support it yet.
      "proxy",
      // the peerName, used debugging and console messages.
      "string",
      ]
  },

  // Send a message to the peer.
  'send': {type: "method", value: [{
    // Data channel id. If provided, will be used as the channel label.
    // If the channel label doesn't already exist, a new channel will be
    // created.
    "channelLabel": "string",
    // One of the bellow should be defined; this is the data to send.
    "text": "string",
    "binary": "blob",
    "buffer": "buffer"
  }]},

  // Called when we get a message from the peer.
  'onReceived': {type: "event", value: {
    // The label/id of the data channel.
    "channelLabel": "string",
    // One the below will be specified.
    "text": "string",
    "binary": "blob",
    "buffer": "buffer"
  }},

  // Open the data channel with this label.
  'openDataChannel': {type: "method", value: ["string"]},
  // Close the data channel with this label.
  'closeDataChannel': {type: "method", value: ["string"]},

  // A channel with this id has been opened.
  'onOpenDataChannel': {type: "event", value: {"channelId": "string"},
  // The channale with this id has been closed.
  'onCloseDataChannel': {type: "event", value: {"channelId": "string"}},

  // Close the peer connection.
  'close': {type: "method", value: []},
  // The peer connection has been closed.
  'onClose': {type: "event", value: {}},
});

<|endoftext|>


<commit_before>import React, { PropTypes } from 'react';
import classnames from 'classnames';
import { filter, find, includes, isArray, isEmpty, orderBy, slice, uniq, without } from 'lodash';
import { StickyContainer, Sticky } from 'react-sticky';

import {
  DEFAULT_FACETED_SEARCH_LENGTH,
  FACET_ATTRIBUTE_SIZE,
  TYPE_ACTION,
  TYPE_CLASSIFICATION,
  TYPE_FUNCTION,
  TYPE_PHASE,
  TYPE_RECORD,
  TYPE_LABELS
} from '../../../config/constants';
import Exporter from '../Exporter';

import { FacetedSearchHelp, FACETED_SEARCH_HELP_TYPE_FACET, FACETED_SEARCH_HELP_TYPE_TERM } from './FacetedSearchHelp/FacetedSearchHelp';
import FacetedSearchResults from './FacetedSearchResults/FacetedSearchResults';
import FacetedSearchSuggestions from './FacetedSearchSuggestions/FacetedSearchSuggestions';
import PreviewItem from './PreviewItem/PreviewItem';
import './FacetedSearch.scss';

export class FacetedSearch extends React.Component {
  constructor (props) {
    super(props);

    this.onClickAllByType = this.onClickAllByType.bind(this);
    this.onClickAttribute = this.onClickAttribute.bind(this);
    this.onClickAttributeOption = this.onClickAttributeOption.bind(this);
    this.onClickItem = this.onClickItem.bind(this);
    this.onClickShowAll = this.onClickShowAll.bind(this);
    this.onClosePreview = this.onClosePreview.bind(this);
    this.onClickReset = this.onClickReset.bind(this);
    this.onSearchSubmit = this.onSearchSubmit.bind(this);
    this.onSearchInputChange = this.onSearchInputChange.bind(this);
    this.onSelectSuggestion = this.onSelectSuggestion.bind(this);
    this.onToggleFacet = this.onToggleFacet.bind(this);

    this.state = {
      facetsOpen: [],
      previewItem: null,
      searchTerm: '',
      selectedFacets: {
        action: [],
        classification: [],
        function: [],
        phase: [],
        record: []
      }
    };
  }

  componentDidMount () {
    this.props.setNavigationVisibility(false);
    const { attributeTypes, isFetching, items } = this.props;
    if (!isEmpty(attributeTypes) && !isFetching && isEmpty(items)) {
      this.props.fetchClassifications();
    }
  }

  componentDidUpdate () {
    const { attributeTypes, isFetching, classifications } = this.props;
    if (!isEmpty(attributeTypes) && !isFetching && isEmpty(classifications)) {
      this.props.fetchClassifications();
    }
  }

  onToggleFacet (type) {
    const { facetsOpen } = this.state;
    this.setState({
      facetsOpen: includes(facetsOpen, type)
        ? without(facetsOpen, type)
        : [...facetsOpen, type]
    });
  }

  onClickShowAll (attribute) {
    this.props.toggleShowAllAttributeOptions(attribute);
  }

  onClickAllByType (type) {
    const { searchTerm } = this.state;
    this.props.searchItems(searchTerm, false, type);
  }

  onClickAttribute (attribute) {
    this.props.toggleAttributeOpen(attribute);
  }

  onClickAttributeOption (attribute, option) {
    const { key, name, type } = attribute;
    const { value, hits } = option;
    const { selectedFacets } = this.state;
    const facets = selectedFacets[type];
    if (facets) {
      const facet = find(facets, { key, value });
      this.setState({
        selectedFacets: {
          ...selectedFacets,
          [type]: facet
            ? without(facets, facet)
            : [...facets, { key, name, type, value, hits }]
        }
      }, () => {
        this.props.filterItems(this.state.selectedFacets);
      });
    }
  }

  onClickItem (item) {
    this.setState({
      previewItem: item
    });
  }

  onClickReset () {
    this.setState({
      facetsOpen: [],
      previewItem: null,
      searchTerm: '',
      selectedFacets: {
        action: [],
        classification: [],
        function: [],
        phase: [],
        record: []
      }
    }, () => {
      this.props.searchItems('');
    });
  }

  onClosePreview () {
    this.setState({
      previewItem: null
    });
  }

  onRemoveFacet (key, type, value) {
    const { selectedFacets } = this.state;
    const facets = selectedFacets[type];
    if (facets) {
      const facet = find(facets, { key, value });
      if (facet) {
        this.setState({
          selectedFacets: {
            ...selectedFacets,
            [type]: without(facets, facet)
          }
        }, () => {
          this.props.filterItems(this.state.selectedFacets);
        });
      }
    }
  }

  onSearchInputChange (e) {
    const suggestSize = FACETED_SEARCH_LENGTH || DEFAULT_FACETED_SEARCH_LENGTH;
    this.setState({ searchTerm: e.target.value }, () => {
      if (this.state.searchTerm.length >= suggestSize) {
        this.props.searchItems(this.state.searchTerm, true);
      } else {
        this.props.resetSuggestions();
      }
    });
  }

  onSearchSubmit (event) {
    event.preventDefault();
    const { searchTerm } = this.state;
    this.setState({
      facetsOpen: [],
      previewItem: null,
      selectedFacets: {
        action: [],
        classification: [],
        function: [],
        phase: [],
        record: []
      }
    }, () => {
      this.props.searchItems(searchTerm);
    });
  }

  onSelectSuggestion (type) {
    const { searchTerm } = this.state;
    this.setState({
      facetsOpen: [type]
    }, () => {
      this.props.searchItems(searchTerm, false, type);
    });
  }

  isOptionSelected (attribute, option) {
    const { key, type } = attribute;
    const { value } = option;
    const facets = this.state.selectedFacets[type];
    const facetOption = find(facets, facet => facet.key === key && facet.value === value);
    return !!facetOption;
  }

  renderAttribute (attribute) {
    const orderedOptions = orderBy(attribute.options, val => val.hits.length, ['desc']);
    const options = attribute.showAll ? orderedOptions : slice(orderedOptions, 0, FACET_ATTRIBUTE_SIZE);
    const hidden = attribute.options.length - options.length;
    const total = uniq(attribute.options.reduce((acc, val) => {
      acc.push(...val.hits);
      return acc;
    }, [])).length;
    return (

 this.onClickAttribute(attribute)}
        >

          {attribute.name}
          ({total})

        {attribute.open && options.map((option, index) => (
          <div
            className={classnames(
              'faceted-search-facets-item-attribute-value',
              { 'faceted-search-facets-item-attribute-value-selected': this.isOptionSelected(attribute, option) }
            )}
            key={`${attribute.type}-${attribute.key}-${option.value}-${index}`}
            onClick={() => this.onClickAttributeOption(attribute, option)}
          >

            {isArray(option.value) ? option.value.join(', ') : option.value}
            ({option.hits.length})

        ))}
        {attribute.open && orderedOptions.length > FACET_ATTRIBUTE_SIZE && (

 this.onClickShowAll(attribute)}
          >
            {attribute.showAll ? 'Vähemmän' : `Lisää (${hidden})`}

        )}

    );
  }

  renderFacetGroup (type) {
    const { attributes } = this.props;
    const { facetsOpen } = this.state;
    const typeAttributes = filter(attributes, { type });
    const orderedAttributes = orderBy(typeAttributes, attr => {
      const hits = attr.options.reduce((acc, val) => {
        acc.push(...val.hits);
        return acc;
      }, []);
      return uniq(hits).length;
    }, ['desc']);
    const isOpen = includes(facetsOpen, type);
    const hits = typeAttributes.reduce((acc, attr) => {
      const h = attr.options.reduce((hitAcc, val) => {
        hitAcc.push(...val.hits);
        return hitAcc;
      }, []);
      acc.push(...h);
      return acc;
    }, []);
    const totalHits = uniq(hits).length;

    return (
      <div className='faceted-search-facets-item' key={`facet-${type}`}>

 this.onToggleFacet(type)}
        >
          {TYPE_LABELS[type]} ({totalHits})
          <button className='btn btn-link'>

          </button>

        {isOpen && !isEmpty(orderedAttributes) && (

 this.onClickAllByType(type)}
              >
                Kaikki
                ({totalHits})

            {orderedAttributes.map(attribute => this.renderAttribute(attribute))}

        )}

    );
  }

  renderSelectedFacetButton (facet) {
    const { key, name, type, value } = facet;
    const valueStr = isArray(value) ? value.join(',') : value;
    const text = `${TYPE_LABELS[type]} / ${name}: ${valueStr}`;
    return (
      <button
        className='btn btn-sm btn-default'
        key={`${type}-${key}-${value}`}
        onClick={() => this.onRemoveFacet(key, type, value)}
      >
        {text}

        {text}
      </button>
    );
  }

  renderSelectedFacets () {
    const { terms } = this.props;
    const { selectedFacets } = this.state;
    return (

        {(!isEmpty(terms) || !isEmpty(selectedFacets.classification) ||
          !isEmpty(selectedFacets.function) || !isEmpty(selectedFacets.phase) ||
          !isEmpty(selectedFacets.action) || !isEmpty(selectedFacets.record)) && (
          <button className='btn btn-sm btn-primary' onClick={this.onClickReset}>Poista rajaukset</button>
        )}
        {selectedFacets.classification.map(facet => this.renderSelectedFacetButton(facet))}
        {selectedFacets.function.map(facet => this.renderSelectedFacetButton(facet))}
        {selectedFacets.phase.map(facet => this.renderSelectedFacetButton(facet))}
        {selectedFacets.action.map(facet => this.renderSelectedFacetButton(facet))}
        {selectedFacets.record.map(facet => this.renderSelectedFacetButton(facet))}

    );
  }

  render () {
    const { attributeTypes, exportItems, isFetching, items, metadata, suggestions } = this.props;
    const { previewItem, searchTerm } = this.state;

    return (
      <StickyContainer>

Sisältöhaku

              <Exporter
                attributeTypes={attributeTypes}
                data={exportItems}
                className='pull-right'
                isVisible={exportItems.length > 0}
              />

              <form onSubmit={this.onSearchSubmit}>
                <input className='input-title form-control col-xs-11'
                  type='search'
                  placeholder='Vapaasanahaku'
                  onChange={this.onSearchInputChange}
                  ref={field => { this.searchField = field; }}
                  value={searchTerm}
                />
                {!isEmpty(suggestions) && (
                  <FacetedSearchSuggestions
                    onSelect={this.onSelectSuggestion}
                    suggestions={suggestions}
                    term={searchTerm}
                  />
                )}
              </form>
              <button className='btn btn-primary' onClick={this.onSearchSubmit}>Hae</button>
              <FacetedSearchHelp type={FACETED_SEARCH_HELP_TYPE_TERM} />

                  Rajaa hakua
                  <FacetedSearchHelp type={FACETED_SEARCH_HELP_TYPE_FACET} />

                  {this.renderFacetGroup(TYPE_CLASSIFICATION)}
                  {this.renderFacetGroup(TYPE_FUNCTION)}
                  {this.renderFacetGroup(TYPE_PHASE)}
                  {this.renderFacetGroup(TYPE_ACTION)}
                  {this.renderFacetGroup(TYPE_RECORD)}

                {isFetching &&

}
                {this.renderSelectedFacets()}
                <FacetedSearchResults
                  highlightedId={previewItem ? previewItem.id : null}
                  items={items}
                  metadata={metadata}
                  onSelectItem={this.onClickItem}
                />

            {!!previewItem && (
              <Sticky>
                <PreviewItem item={previewItem} metadata={metadata} onClose={this.onClosePreview} />
              </Sticky>
            )}

      </StickyContainer>
    );
  }
}

FacetedSearch.propTypes = {
  attributeTypes: PropTypes.object.isRequired,
  attributes: PropTypes.array.isRequired,
  classifications: PropTypes.array.isRequired,
  exportItems: PropTypes.array.isRequired,
  fetchClassifications: PropTypes.func.isRequired,
  filterItems: PropTypes.func.isRequired,
  isFetching: PropTypes.bool,
  items: PropTypes.array.isRequired,
  metadata: PropTypes.object.isRequired,
  resetSuggestions: PropTypes.func.isRequired,
  searchItems: PropTypes.func.isRequired,
  setNavigationVisibility: PropTypes.func.isRequired,
  suggestions: PropTypes.array.isRequired,
  terms: PropTypes.array.isRequired,
  toggleAttributeOpen: PropTypes.func.isRequired,
  toggleShowAllAttributeOptions: PropTypes.func.isRequired
};

export default FacetedSearch;
<commit_msg>Faceted search: attribute values in alphabetic order<commit_after>import React, { PropTypes } from 'react';
import classnames from 'classnames';
import { filter, find, includes, isArray, isEmpty, orderBy, slice, uniq, without } from 'lodash';
import { StickyContainer, Sticky } from 'react-sticky';

import {
  DEFAULT_FACETED_SEARCH_LENGTH,
  FACET_ATTRIBUTE_SIZE,
  TYPE_ACTION,
  TYPE_CLASSIFICATION,
  TYPE_FUNCTION,
  TYPE_PHASE,
  TYPE_RECORD,
  TYPE_LABELS
} from '../../../config/constants';
import Exporter from '../Exporter';

import { FacetedSearchHelp, FACETED_SEARCH_HELP_TYPE_FACET, FACETED_SEARCH_HELP_TYPE_TERM } from './FacetedSearchHelp/FacetedSearchHelp';
import FacetedSearchResults from './FacetedSearchResults/FacetedSearchResults';
import FacetedSearchSuggestions from './FacetedSearchSuggestions/FacetedSearchSuggestions';
import PreviewItem from './PreviewItem/PreviewItem';
import './FacetedSearch.scss';

export class FacetedSearch extends React.Component {
  constructor (props) {
    super(props);

    this.onClickAllByType = this.onClickAllByType.bind(this);
    this.onClickAttribute = this.onClickAttribute.bind(this);
    this.onClickAttributeOption = this.onClickAttributeOption.bind(this);
    this.onClickItem = this.onClickItem.bind(this);
    this.onClickShowAll = this.onClickShowAll.bind(this);
    this.onClosePreview = this.onClosePreview.bind(this);
    this.onClickReset = this.onClickReset.bind(this);
    this.onSearchSubmit = this.onSearchSubmit.bind(this);
    this.onSearchInputChange = this.onSearchInputChange.bind(this);
    this.onSelectSuggestion = this.onSelectSuggestion.bind(this);
    this.onToggleFacet = this.onToggleFacet.bind(this);

    this.state = {
      facetsOpen: [],
      previewItem: null,
      searchTerm: '',
      selectedFacets: {
        action: [],
        classification: [],
        function: [],
        phase: [],
        record: []
      }
    };
  }

  componentDidMount () {
    this.props.setNavigationVisibility(false);
    const { attributeTypes, isFetching, items } = this.props;
    if (!isEmpty(attributeTypes) && !isFetching && isEmpty(items)) {
      this.props.fetchClassifications();
    }
  }

  componentDidUpdate () {
    const { attributeTypes, isFetching, classifications } = this.props;
    if (!isEmpty(attributeTypes) && !isFetching && isEmpty(classifications)) {
      this.props.fetchClassifications();
    }
  }

  onToggleFacet (type) {
    const { facetsOpen } = this.state;
    this.setState({
      facetsOpen: includes(facetsOpen, type)
        ? without(facetsOpen, type)
        : [...facetsOpen, type]
    });
  }

  onClickShowAll (attribute) {
    this.props.toggleShowAllAttributeOptions(attribute);
  }

  onClickAllByType (type) {
    const { searchTerm } = this.state;
    this.props.searchItems(searchTerm, false, type);
  }

  onClickAttribute (attribute) {
    this.props.toggleAttributeOpen(attribute);
  }

  onClickAttributeOption (attribute, option) {
    const { key, name, type } = attribute;
    const { value, hits } = option;
    const { selectedFacets } = this.state;
    const facets = selectedFacets[type];
    if (facets) {
      const facet = find(facets, { key, value });
      this.setState({
        selectedFacets: {
          ...selectedFacets,
          [type]: facet
            ? without(facets, facet)
            : [...facets, { key, name, type, value, hits }]
        }
      }, () => {
        this.props.filterItems(this.state.selectedFacets);
      });
    }
  }

  onClickItem (item) {
    this.setState({
      previewItem: item
    });
  }

  onClickReset () {
    this.setState({
      facetsOpen: [],
      previewItem: null,
      searchTerm: '',
      selectedFacets: {
        action: [],
        classification: [],
        function: [],
        phase: [],
        record: []
      }
    }, () => {
      this.props.searchItems('');
    });
  }

  onClosePreview () {
    this.setState({
      previewItem: null
    });
  }

  onRemoveFacet (key, type, value) {
    const { selectedFacets } = this.state;
    const facets = selectedFacets[type];
    if (facets) {
      const facet = find(facets, { key, value });
      if (facet) {
        this.setState({
          selectedFacets: {
            ...selectedFacets,
            [type]: without(facets, facet)
          }
        }, () => {
          this.props.filterItems(this.state.selectedFacets);
        });
      }
    }
  }

  onSearchInputChange (e) {
    const suggestSize = FACETED_SEARCH_LENGTH || DEFAULT_FACETED_SEARCH_LENGTH;
    this.setState({ searchTerm: e.target.value }, () => {
      if (this.state.searchTerm.length >= suggestSize) {
        this.props.searchItems(this.state.searchTerm, true);
      } else {
        this.props.resetSuggestions();
      }
    });
  }

  onSearchSubmit (event) {
    event.preventDefault();
    const { searchTerm } = this.state;
    this.setState({
      facetsOpen: [],
      previewItem: null,
      selectedFacets: {
        action: [],
        classification: [],
        function: [],
        phase: [],
        record: []
      }
    }, () => {
      this.props.searchItems(searchTerm);
    });
  }

  onSelectSuggestion (type) {
    const { searchTerm } = this.state;
    this.setState({
      facetsOpen: [type]
    }, () => {
      this.props.searchItems(searchTerm, false, type);
    });
  }

  isOptionSelected (attribute, option) {
    const { key, type } = attribute;
    const { value } = option;
    const facets = this.state.selectedFacets[type];
    const facetOption = find(facets, facet => facet.key === key && facet.value === value);
    return !!facetOption;
  }

  renderAttribute (attribute) {
    const orderedOptions = orderBy(attribute.options, option => option.ref, ['asc']);
    const options = attribute.showAll ? orderedOptions : slice(orderedOptions, 0, FACET_ATTRIBUTE_SIZE);
    const hidden = attribute.options.length - options.length;
    const total = uniq(attribute.options.reduce((acc, val) => {
      acc.push(...val.hits);
      return acc;
    }, [])).length;
    return (

 this.onClickAttribute(attribute)}
        >

          {attribute.name}
          ({total})

        {attribute.open && options.map((option, index) => (
          <div
            className={classnames(
              'faceted-search-facets-item-attribute-value',
              { 'faceted-search-facets-item-attribute-value-selected': this.isOptionSelected(attribute, option) }
            )}
            key={`${attribute.type}-${attribute.key}-${option.value}-${index}`}
            onClick={() => this.onClickAttributeOption(attribute, option)}
          >

            {isArray(option.value) ? option.value.join(', ') : option.value}
            ({option.hits.length})

        ))}
        {attribute.open && orderedOptions.length > FACET_ATTRIBUTE_SIZE && (

 this.onClickShowAll(attribute)}
          >
            {attribute.showAll ? 'Vähemmän' : `Lisää (${hidden})`}

        )}

    );
  }

  renderFacetGroup (type) {
    const { attributes } = this.props;
    const { facetsOpen } = this.state;
    const typeAttributes = filter(attributes, { type });
    const orderedAttributes = orderBy(typeAttributes, attr => {
      const hits = attr.options.reduce((acc, val) => {
        acc.push(...val.hits);
        return acc;
      }, []);
      return uniq(hits).length;
    }, ['desc']);
    const isOpen = includes(facetsOpen, type);
    const hits = typeAttributes.reduce((acc, attr) => {
      const h = attr.options.reduce((hitAcc, val) => {
        hitAcc.push(...val.hits);
        return hitAcc;
      }, []);
      acc.push(...h);
      return acc;
    }, []);
    const totalHits = uniq(hits).length;

    return (
      <div className='faceted-search-facets-item' key={`facet-${type}`}>

 this.onToggleFacet(type)}
        >
          {TYPE_LABELS[type]} ({totalHits})
          <button className='btn btn-link'>

          </button>

        {isOpen && !isEmpty(orderedAttributes) && (

 this.onClickAllByType(type)}
              >
                Kaikki
                ({totalHits})

            {orderedAttributes.map(attribute => this.renderAttribute(attribute))}

        )}

    );
  }

  renderSelectedFacetButton (facet) {
    const { key, name, type, value } = facet;
    const valueStr = isArray(value) ? value.join(',') : value;
    const text = `${TYPE_LABELS[type]} / ${name}: ${valueStr}`;
    return (
      <button
        className='btn btn-sm btn-default'
        key={`${type}-${key}-${value}`}
        onClick={() => this.onRemoveFacet(key, type, value)}
      >
        {text}

        {text}
      </button>
    );
  }

  renderSelectedFacets () {
    const { terms } = this.props;
    const { selectedFacets } = this.state;
    return (

        {(!isEmpty(terms) || !isEmpty(selectedFacets.classification) ||
          !isEmpty(selectedFacets.function) || !isEmpty(selectedFacets.phase) ||
          !isEmpty(selectedFacets.action) || !isEmpty(selectedFacets.record)) && (
          <button className='btn btn-sm btn-primary' onClick={this.onClickReset}>Poista rajaukset</button>
        )}
        {selectedFacets.classification.map(facet => this.renderSelectedFacetButton(facet))}
        {selectedFacets.function.map(facet => this.renderSelectedFacetButton(facet))}
        {selectedFacets.phase.map(facet => this.renderSelectedFacetButton(facet))}
        {selectedFacets.action.map(facet => this.renderSelectedFacetButton(facet))}
        {selectedFacets.record.map(facet => this.renderSelectedFacetButton(facet))}

    );
  }

  render () {
    const { attributeTypes, exportItems, isFetching, items, metadata, suggestions } = this.props;
    const { previewItem, searchTerm } = this.state;

    return (
      <StickyContainer>

Sisältöhaku

              <Exporter
                attributeTypes={attributeTypes}
                data={exportItems}
                className='pull-right'
                isVisible={exportItems.length > 0}
              />

              <form onSubmit={this.onSearchSubmit}>
                <input className='input-title form-control col-xs-11'
                  type='search'
                  placeholder='Vapaasanahaku'
                  onChange={this.onSearchInputChange}
                  ref={field => { this.searchField = field; }}
                  value={searchTerm}
                />
                {!isEmpty(suggestions) && (
                  <FacetedSearchSuggestions
                    onSelect={this.onSelectSuggestion}
                    suggestions={suggestions}
                    term={searchTerm}
                  />
                )}
              </form>
              <button className='btn btn-primary' onClick={this.onSearchSubmit}>Hae</button>
              <FacetedSearchHelp type={FACETED_SEARCH_HELP_TYPE_TERM} />

                  Rajaa hakua
                  <FacetedSearchHelp type={FACETED_SEARCH_HELP_TYPE_FACET} />

                  {this.renderFacetGroup(TYPE_CLASSIFICATION)}
                  {this.renderFacetGroup(TYPE_FUNCTION)}
                  {this.renderFacetGroup(TYPE_PHASE)}
                  {this.renderFacetGroup(TYPE_ACTION)}
                  {this.renderFacetGroup(TYPE_RECORD)}

                {isFetching &&

}
                {this.renderSelectedFacets()}
                <FacetedSearchResults
                  highlightedId={previewItem ? previewItem.id : null}
                  items={items}
                  metadata={metadata}
                  onSelectItem={this.onClickItem}
                />

            {!!previewItem && (
              <Sticky>
                <PreviewItem item={previewItem} metadata={metadata} onClose={this.onClosePreview} />
              </Sticky>
            )}

      </StickyContainer>
    );
  }
}

FacetedSearch.propTypes = {
  attributeTypes: PropTypes.object.isRequired,
  attributes: PropTypes.array.isRequired,
  classifications: PropTypes.array.isRequired,
  exportItems: PropTypes.array.isRequired,
  fetchClassifications: PropTypes.func.isRequired,
  filterItems: PropTypes.func.isRequired,
  isFetching: PropTypes.bool,
  items: PropTypes.array.isRequired,
  metadata: PropTypes.object.isRequired,
  resetSuggestions: PropTypes.func.isRequired,
  searchItems: PropTypes.func.isRequired,
  setNavigationVisibility: PropTypes.func.isRequired,
  suggestions: PropTypes.array.isRequired,
  terms: PropTypes.array.isRequired,
  toggleAttributeOpen: PropTypes.func.isRequired,
  toggleShowAllAttributeOptions: PropTypes.func.isRequired
};

export default FacetedSearch;
<|endoftext|>


<commit_before>const canvas = document.getElementById("screen");
const ctx = canvas.getContext("2d");

const state = {
  dirty: true,
  screen: {
    width: window.innerWidth,
    height: window.innerHeight,
  },
};

const resizeCanvas = () => {
  if (
    state.screen.width !== window.innerWidth ||
    state.screen.height !== window.innerHeight
  ) {
    state.screen.width = window.innerWidth;
    state.screen.height = window.innerHeight;
    state.dirty = true;
  }
};

const draw = () => {
  ctx.fillStyle = "rgb(200, 0, 0)";
  ctx.fillRect(10, 10, 50, 50);

  ctx.fillStyle = "rgba(0, 0, 200, 0.5)";
  ctx.fillRect(30, 30, 50, 50);
};

// ---------------------------

const render = () => {
  if (state.dirty) {
    state.dirty = false;
    draw();
  }

  window.requestAnimationFrame(render);
};

window.requestAnimationFrame(render);
window.addEventListener("resize", resizeCanvas, false);
<commit_msg>Az egér lenyomást és pozíciót is nyilvántartom most már<commit_after>const canvas = document.getElementById("screen");
const ctx = canvas.getContext("2d");

const state = {
  dirty: true,
  screen: {
    width: 0,
    height: 0,
  },
  mouse: {
    pressed: false,
    posX: 0,
    posY: 0,
  },
};

const renderScene = () => {
  ctx.fillStyle = "#eaeaea";
  ctx.fillRect(0, 0, state.screen.width, state.screen.height);

  ctx.fillStyle = "rgb(200, 0, 0)";
  ctx.fillRect(10, 10, 50, 50);

  ctx.fillStyle = "rgba(0, 0, 200, 0.5)";
  ctx.fillRect(30, 30, 50, 50);
};

const mainLoop = () => {
  if (state.dirty) {
    state.dirty = false;
    renderScene();
  }

  window.requestAnimationFrame(mainLoop);
};

const resizeCanvas = () => {
  if (
    state.screen.width !== window.innerWidth ||
    state.screen.height !== window.innerHeight
  ) {
    state.screen.width = window.innerWidth;
    state.screen.height = window.innerHeight;

    state.dirty = true;

    canvas.width = state.screen.width;
    canvas.height = state.screen.height;
  }
};

window.requestAnimationFrame(mainLoop);

window.addEventListener("resize", resizeCanvas);
resizeCanvas();
canvas.addEventListener("mousedown", (e) => {
  if (e.button === 0) {
    state.mouse.pressed = true;
    state.dirty = true;
  }
});
canvas.addEventListener("mouseup", (e) => {
  if (e.button === 0) {
    state.mouse.pressed = false;
    state.dirty = true;
  }
});
canvas.addEventListener("mousemove", (e) => {
  if (state.mouse.pressed) {
    state.mouse.posX = e.clientX;
    state.mouse.posY = e.clientY;
    state.dirty = true;
  }
});
<|endoftext|>


<commit_before>$('.select-course table tbody tr .sectionNumber').each(function () {
  var nameElem = $(this).next().next().next();
  getPolyratingData(nameElem);
});

function getPolyratingData(nameElem) {
  if (nameElem[0].innerText === 'STAFF') {
    return;
  }
  var names = nameElem[0].innerText.split(',');
  var lastName = removeAllSingleLetters(names[0].trim());
  var firstName = onlyFirstName(removeAllSingleLetters(names[1].trim()));
  var name = firstName + ' ' + lastName;
  getDataAndUpdatePage(nameElem, name, lastName, true);
}

function getDataAndUpdatePage(nameElem, name, lastName, firstAttempt) {
  chrome.runtime.sendMessage(
    {
      method: 'GET',
      action: 'xhttp',
      url: 'http://polyratings.com/search.php?type=ProfName&terms=' + (firstAttempt ? name.replace(/ /g, '+') : lastName) + '&format=long&sort=name',
      data: '',
    },
    function (response) {
      if (response != 'error') {
        try {
          var polyratingPage = $($.parseHTML(response));
          var ratingElem = polyratingPage.find('.hidden-xs span .text-primary');
          var rating = ratingElem[0].innerText;
          var profId = response.match(/profid=(\d+)/)[1];
          var evals = ratingElem.next()[0].innerText.replace('uation', '');
          var href = 'http://polyratings.com/eval.php?profid=' + profId;
          var numericalRating = parseFloat(rating, 10);
          nameElem.css('background', calculateBgRGBA(numericalRating));
          nameElem.html('' + nameElem.html() + '
' + rating + ' (' + evals + ')');
          return;
        } catch (e) { }
      }
      if (firstAttempt) {
        getDataAndUpdatePage(nameElem, name, lastName, false);
      } else {
        addLinkToSearchPage(nameElem, lastName);
      }
    }
  );
}

function onlyFirstName(name) {
  var spaceIndex = name.indexOf(' ');
  return spaceIndex > -1 ? name.substr(0, spaceIndex) : name;
}

function removeAllSingleLetters(name) {
  var modified = '';
  var parts = name.split(' ');
  parts.forEach(function (part) {
    if (/[a-z]{2,}/.test(part)) {
      modified += ' ' + part;
    }
  });
  return modified.trim();
}

function calculateBgRGBA(rating) {
  var r = rating < 3 ? 255 : 255 * (4 - rating) / 4;
  var g = rating < 3 ? 255 * rating / 3 : 255;
  return 'rgba(' + Math.round(r) + ',' + Math.round(g) + ',0,0.6)';
}

function addLinkToSearchPage(nameElem, lastName) {
  var href = 'http://polyratings.com/search.php?type=ProfName&terms=' + lastName + '&format=long&sort=name';
  nameElem.html('' + nameElem.html() + '');
}
<commit_msg>speed up extension by reducing number of http requests<commit_after>(function() {
  var nameGroups = {};

  $('.select-course table tbody tr .sectionNumber').each(function () {
    var nameElem = $(this).next().next().next();
    var rawName = nameElem[0].innerText;
    if (rawName !== 'STAFF') {
      if (nameGroups[rawName]) {
        nameGroups[rawName].push(nameElem);
      } else {
        nameGroups[rawName] = [nameElem];
      }
    }
  });

  for (var rawName in nameGroups) {
    getPolyratingData(nameGroups[rawName]);
  }

  function getPolyratingData(nameElems) {
    var names = nameElems[0][0].innerText.split(',');
    var lastName = removeAllSingleLetters(names[0].trim());
    var firstName = onlyFirstName(removeAllSingleLetters(names[1].trim()));
    var name = firstName + ' ' + lastName;
    console.log(name);
    getDataAndUpdatePage(nameElems, name, lastName, true);
  }

  function getDataAndUpdatePage(nameElems, name, lastName, firstAttempt) {
    chrome.runtime.sendMessage(
      {
        method: 'GET',
        action: 'xhttp',
        url: 'http://polyratings.com/search.php?type=ProfName&terms=' + (firstAttempt ? name.replace(/ /g, '+') : lastName) + '&format=long&sort=name',
        data: '',
      },
      function (response) {
        if (response != 'error') {
          try {
            var polyratingPage = $($.parseHTML(response));
            var ratingElem = polyratingPage.find('.hidden-xs span .text-primary');
            var rating = ratingElem[0].innerText;
            var profId = response.match(/profid=(\d+)/)[1];
            var evals = ratingElem.next()[0].innerText.replace('uation', '');
            var href = 'http://polyratings.com/eval.php?profid=' + profId;
            var numericalRating = parseFloat(rating, 10);
            var bgColor = calculateBgRGBA(numericalRating);
            nameElems.forEach(function(nameElem) {
              nameElem.css('background', bgColor);
              nameElem.html('' + nameElem.html() + '
' + rating + ' (' + evals + ')');
            });
            return;
          } catch (e) { }
        }
        if (firstAttempt) {
          getDataAndUpdatePage(nameElems, name, lastName, false);
        } else {
          addLinkToSearchPage(nameElems, lastName);
        }
      }
    );
  }

  function onlyFirstName(name) {
    var spaceIndex = name.indexOf(' ');
    return spaceIndex > -1 ? name.substr(0, spaceIndex) : name;
  }

  function removeAllSingleLetters(name) {
    var modified = '';
    var parts = name.split(' ');
    parts.forEach(function (part) {
      if (/[a-z]{2,}/.test(part)) {
        modified += ' ' + part;
      }
    });
    return modified.trim();
  }

  function calculateBgRGBA(rating) {
    var r = rating < 3 ? 255 : 255 * (4 - rating) / 4;
    var g = rating < 3 ? 255 * rating / 3 : 255;
    return 'rgba(' + Math.round(r) + ',' + Math.round(g) + ',0,0.6)';
  }

  function addLinkToSearchPage(nameElems, lastName) {
    var href = 'http://polyratings.com/search.php?type=ProfName&terms=' + lastName + '&format=long&sort=name';
    nameElems.forEach(function(nameElem) {
      nameElem.html('' + nameElem.html() + '');
    });
  }
}());
<|endoftext|>