<commit_before>var stream = require('flyd').stream;
var dropRepeats = require('flyd-droprepeats').dropRepeats;
var dropRepeatsWith = require('flyd-droprepeats').dropRepeatsWith;
var keycode = require('keycode');
var flyd = require('flyd');
var scanMerge = require('flyd-scanmerge');

exports.keysDown = keysDown = function() {
  var kds = stream();
  var kus = stream();
  var keysDown = state(kds, kus);

  document.addEventListener('keydown', function(ev) {
    // Prevent repeated events for the same key.
    // dropRepeats can't be used on `kds` because it won't deactivate after
    // `kus` has fired for the same key. Other (heavier?) option would be to
    // do dropRepeatsWith(deepEqual) for keysDown.
    var c = ev.keyCode;
    if (keysDown().indexOf(c) < 0) kds(c);
  }, false);

  document.addEventListener('keyup', function(ev) {
    kus(ev.keyCode);
  }, false);

  return keysDown;
};

exports.keyDowns = function() {
  var prev = [];
  var keysDown$ = keysDown();
  return stream([keysDown$], function(self) {
    var cur  = keysDown$();
    var last = cur.slice(-1)[0];
    if (last && prev.indexOf(last) < 0)
      self(last);
    prev = cur;
  });
};

exports.presses = function() {
  var presses = stream();

  document.addEventListener('keypress', function(ev) {
    presses(ev.keyCode);
  }, false);

  return presses;
};

exports.key = function(key) {
  var ks = stream(false);
  var code = keycode(key);

  document.addEventListener('keydown', function(ev) {
    if (ev.keyCode === code) ks(true);
  }, false);

  document.addEventListener('keyup', function(ev) {
    if (ev.keyCode === code) ks(false);
  }, false);

  return dropRepeats(ks);
};

exports.arrows = function(elem) {
  var l = stream(false);
  var r = stream(false);
  var u = stream(false);
  var d = stream(false);

  document.addEventListener('keydown', function(ev) {
    if      (ev.keyCode === 37) l(true);
    else if (ev.keyCode === 39) r(true);
    else if (ev.keyCode === 38) u(true);
    else if (ev.keyCode === 40) d(true);
  }, false);

  document.addEventListener('keyup', function(ev) {
    if      (ev.keyCode === 37) l(false);
    else if (ev.keyCode === 39) r(false);
    else if (ev.keyCode === 38) u(false);
    else if (ev.keyCode === 40) d(false);
  }, false);

  return dropRepeatsWith(eqCoords, stream([l, r, u, d], function() {
    return {
      x: l() ? -1 : r() ? 1 : 0,
      y: u() ? -1 : d() ? 1 : 0
    };
  }));
};

function eqCoords(a, b) {
  return a && b && a.x === b.x && a.y === b.y;
};

function state(add, remove) {
  return scanMerge([
    [add, function(mem, a) {
      return mem.concat(a);
    }],
    [remove, function(mem, a) {
      return mem.filter(function(b) {
        return a !== b;
      });
    }],
  ], []);
}
<commit_msg>Add wasd export. aka copy arrows.<commit_after>var stream = require('flyd').stream;
var dropRepeats = require('flyd-droprepeats').dropRepeats;
var dropRepeatsWith = require('flyd-droprepeats').dropRepeatsWith;
var keycode = require('keycode');
var flyd = require('flyd');
var scanMerge = require('flyd-scanmerge');

exports.keysDown = keysDown = function() {
  var kds = stream();
  var kus = stream();
  var keysDown = state(kds, kus);

  document.addEventListener('keydown', function(ev) {
    // Prevent repeated events for the same key.
    // dropRepeats can't be used on `kds` because it won't deactivate after
    // `kus` has fired for the same key. Other (heavier?) option would be to
    // do dropRepeatsWith(deepEqual) for keysDown.
    var c = ev.keyCode;
    if (keysDown().indexOf(c) < 0) kds(c);
  }, false);

  document.addEventListener('keyup', function(ev) {
    kus(ev.keyCode);
  }, false);

  return keysDown;
};

exports.keyDowns = function() {
  var prev = [];
  var keysDown$ = keysDown();
  return stream([keysDown$], function(self) {
    var cur  = keysDown$();
    var last = cur.slice(-1)[0];
    if (last && prev.indexOf(last) < 0)
      self(last);
    prev = cur;
  });
};

exports.presses = function() {
  var presses = stream();

  document.addEventListener('keypress', function(ev) {
    presses(ev.keyCode);
  }, false);

  return presses;
};

exports.key = function(key) {
  var ks = stream(false);
  var code = keycode(key);

  document.addEventListener('keydown', function(ev) {
    if (ev.keyCode === code) ks(true);
  }, false);

  document.addEventListener('keyup', function(ev) {
    if (ev.keyCode === code) ks(false);
  }, false);

  return dropRepeats(ks);
};

exports.arrows = function(elem) {
  console.log("BLARG");
  var l = stream(false);
  var r = stream(false);
  var u = stream(false);
  var d = stream(false);

  document.addEventListener('keydown', function(ev) {
    if      (ev.keyCode === 37) l(true);
    else if (ev.keyCode === 39) r(true);
    else if (ev.keyCode === 38) u(true);
    else if (ev.keyCode === 40) d(true);
  }, false);

  document.addEventListener('keyup', function(ev) {
    if      (ev.keyCode === 37) l(false);
    else if (ev.keyCode === 39) r(false);
    else if (ev.keyCode === 38) u(false);
    else if (ev.keyCode === 40) d(false);
  }, false);

  return dropRepeatsWith(eqCoords, stream([l, r, u, d], function() {
    return {
      x: l() ? -1 : r() ? 1 : 0,
      y: u() ? -1 : d() ? 1 : 0
    };
  }));
};

exports.wasd = function(elem) {
  var w = stream(false); // 88
  var a = stream(false); // 65
  var s = stream(false); // 83
  var d = stream(false); // 68

  document.addEventListener('keydown', function(ev) {
    if      (ev.keyCode === 88) w(true);
    else if (ev.keyCode === 65) a(true);
    else if (ev.keyCode === 83) s(true);
    else if (ev.keyCode === 68) d(true);
  }, false);

  document.addEventListener('keyup', function(ev) {
    if      (ev.keyCode === 88) w(false);
    else if (ev.keyCode === 65) a(false);
    else if (ev.keyCode === 83) s(false);
    else if (ev.keyCode === 68) d(false);
  }, false);

  return dropRepeatsWith(eqCoords, stream([w, a, s, d], function() {
    return {
      x: a() ? -1 : d() ? 1 : 0,
      y: w() ? -1 : s() ? 1 : 0
    };
  }));
};

functioneqCoords(a, b) {
  return a && b && a.x === b.x && a.y === b.y;
};

function state(add, remove) {
  return scanMerge([
    [add, function(mem, a) {
      return mem.concat(a);
    }],
    [remove, function(mem, a) {
      return mem.filter(function(b) {
        return a !== b;
      });
    }],
  ], []);
}
<|endoftext|>


<commit_before>angular.module('mscApp').controller('usersController',function($scope, $window,  $routeParams, $http) {
    $scope.signup = function () {
        var data = $.param({
            firstname: $scope.firstname,
            lastname: $scope.lastname,
            email: $scope.email,
            country: $scope.country,
            city: $scope.city,
            password: $scope.password,
            action: "signup"
        });

        var config = {
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8;'
            }
        }

        $http.post('/MSc/api/users.php', data, config)
            .success(function (data, status, headers, config) {
                $scope.PostDataResponse = data;
                $window.location.href = data.url;
            })
            .error(function (data, status, header, config) {
                $scope.ResponseDetails = "Data: " + data +
                    "
status: " + status +
                    "
headers: " + header +
                    "
config: " + config;
            })
    };

    $scope.signin = function () {
        var data = $.param({
            email: $scope.email,
            password: $scope.password,
            action: "login"
        });

        var config = {
            headers : {
                'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8;'
            }
        }

        $http.post('/MSc/api/users.php', data, config)
            .success(function (data, status, headers, config) {
                $scope.PostDataResponse = data;
                if (data.success) {
                    $window.location.href = data.url;
                }
            })
            .error(function (data, status, header, config) {
                alert('Errorrrrrrr');
                $scope.ResponseDetails = "Data: " + data +
                    "
status: " + status +
                    "
headers: " + header +
                    "
config: " + config;
            });
    };

    $scope.getUserById = function() {
        $http({
            method: 'GET',
            url: '/MSc/api/actions.php',
            params: { "action" : "getById" , "id" : $routeParams.id }

        }).then(function (response) {

            // on success
            $scope.user = response.data[0];

            console.log($scope.user)

        }, function (response) {

            // on error
            console.log(response.data,response.status);

        });
    };
});<commit_msg>Login Page Testing - Google Library<commit_after>angular.module('mscApp').controller('usersController',function($scope, $window,  $routeParams, $http) {
    $scope.signup = function () {
        var data = $.param({
            firstname: $scope.firstname,
            lastname: $scope.lastname,
            email: $scope.email,
            country: $scope.country,
            city: $scope.city,
            password: $scope.password,
            action: "signup"
        });

        var config = {
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8;'
            }
        }

        $http.post('/MSc/api/users.php', data, config)
            .success(function (data, status, headers, config) {
                $scope.PostDataResponse = data;
                $window.location.href = data.url;
            })
            .error(function (data, status, header, config) {
                $scope.ResponseDetails = "Data: " + data +
                    "
status: " + status +
                    "
headers: " + header +
                    "
config: " + config;
            })
    };

    $scope.signin = function () {
        var data = $.param({
            email: $scope.email,
            password: $scope.password,
            action: "login"
        });

        var config = {
            headers : {
                'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8;'
            }
        }

        $http.post('/MSc/api/users.php', data, config)
            .success(function (data, status, headers, config) {
                $scope.PostDataResponse = data;
                if (data.success) {
                    $window.location.href = data.url;
                }
            })
            .error(function (data, status, header, config) {
                alert('Redirect to login');
                $scope.ResponseDetails = "Data: " + data +
                    "
status: " + status +
                    "
headers: " + header +
                    "
config: " + config;
            });
    };

    $scope.getUserById = function() {
        $http({
            method: 'GET',
            url: '/MSc/api/actions.php',
            params: { "action" : "getById" , "id" : $routeParams.id }

        }).then(function (response) {

            // on success
            $scope.user = response.data[0];

            console.log($scope.user)

        }, function (response) {

            // on error
            console.log(response.data,response.status);

        });
    };
});<|endoftext|>


<commit_before>/*
 * General specifications GridGallery
 * 1- creates a grid using elements that will be in different heights and widths
 * 2- will enable gallery view on click to an element of the grid (dialog)
 * 3- the dialog will trigger custom events to control data in dialog
 * 4- nav of items in grid (disable/enable) depending on initialization
 */

describe('GridGallery', function(){
  var x, options = { lightBox: true };
  var container;
  document.body.className = 'grid-gallery__example';

  beforeEach(function() {
    var wrapper = document.createElement('div');
    wrapper.className = 'container';

    container = document.createElement('div');
    container.className = 'grid-gallery';

    for (var i = 0; i < 5; i++) {
      var element = document.createElement('div');
      var p = document.createElement('p');
      p.textContent = 'lorem nav of items in grid (disable/enable) depending on initialization';
      element.className = 'grid-gallery__item grid-gallery__dummy';
      element.appendChild(p);
      container.appendChild(element);
    }

    wrapper.appendChild(container);
    document.body.appendChild(wrapper);

  });

  afterEach(function(){
    var oldContainer = document.querySelector('.container');
    oldContainer.parentNode.removeChild(oldContainer);
  });

  describe('GridGallery initialization', function(){

    it('Global must have the GridGallery as a property', function() {
      x = new GridGallery(container, options);
     expect(GridGallery).not.toBeUndefined();
    });

    it('should manipulate the default options to the created instance', function() {
      var div = document.createElement('div');
      var childs = document.createElement('div');
      childs.className = 'grid-gallery__item';
      div.appendChild(childs);
      var y = new GridGallery( div, {});
      expect(y.options).toEqual({'lightBox': false});
    });

    it('should extend the default options and icludes the new one', function() {
      x = new GridGallery(container, options);
      expect(x.options).toEqual({'lightBox': true});
    });

    it('should store the container as a property', function() {
      x = new GridGallery(container, options);
      expect(x.element).toEqual(container);
    });

    it('should have method init', function() {
      x = new GridGallery(container, options);
      expect(x.init).toBeDefined();
      expect(typeof x.init).toBe('function');
    });
  })

  describe('init() method', function() {
    it('should update the element and its childrens', function(){
      x = new GridGallery(container, options)
      expect(document.querySelector('.grid-gallery').offsetHeight).not.toBeLessThan(200);
      expect(document.querySelector('.grid-gallery__item').dataset.grid).toBe('true');
    });

    it('should update children positions', function() {
      var items;
      x = new GridGallery(container, options);

      items = document.body.querySelectorAll('.grid-gallery__item');
      for(var i = 0; i < items.length; i++) {
        expect(items[i].style.left).not.toBe('');
      }
    });
  });

  describe('update() method', function(){
    it('should be called on window resize', function(done){
      var event;
      x = new GridGallery(container, options);
      spyOn(x, 'update');

      event = document.createEvent('HTMLEvents');
      event.initEvent('resize', true, false);
      window.dispatchEvent(event);

      setTimeout(function(){
        expect(x.update).toHaveBeenCalled();
        done();
      }, 300);
    });

    it('should enable items and reposition it', function(done) {
      var event;
      x = new GridGallery(container, options);
      event = document.createEvent('HTMLEvents');
      event.initEvent('resize', true, false);
      window.dispatchEvent(event);

      setTimeout(function(){
        var item = x.element.querySelector('.grid-gallery__item');
        expect(item.dataset.grid).toEqual('true');
        done();
      }, 300);
    });

    it('should update positions if DOM changed using observer', function(done){
      var x = new GridGallery(container, options);
      spyOn(x, 'update');
      var div = document.createElement('div');
      var child = document.createElement('div');
      div.id = "dummyId";
      div.className = ['grid-gallery__item'];
      child.className = ' grid-gallery__dummy';
      div.appendChild(child);
      var cont = document.querySelector('.grid-gallery');
      cont.appendChild(div);

      setTimeout(function(){
        expect(document.getElementById('dummyId').dataset.grid).toEqual('true');
        expect(document.getElementById('dummyId').offsetTop).not.toBeLessThan(100);
        done();
      }, 300);
    });

    it('should update positions if DOM changed using EventListener', function(done){
      window.MutationObserver = undefined;
      window.WebKitMutationObserver = undefined;
      var x = new GridGallery(container, options);
      spyOn(x, 'update');
      var div = document.createElement('div');
      var child = document.createElement('div');
      div.id = "dummyId2";
      div.className = ['grid-gallery__item'];
      child.className = ' grid-gallery__dummy';
      div.appendChild(child);
      var cont = document.querySelector('.grid-gallery');
      cont.appendChild(div);

      setTimeout(function(){
        expect(document.getElementById('dummyId2').dataset.grid).toEqual('true');
        expect(document.getElementById('dummyId2').offsetTop).not.toBeLessThan(100);
        done();
      }, 300);
    });
  });

  describe('Private Methods', function() {
    it('Private _checkItems: should be defined', function() {
      x = new GridGallery(container, options);
      expect(x._checkItems).toBeDefined();
    });

    it('Private _checkItems: should return Array of not Enabled items', function() {
      var items;
      x = new GridGallery(container, options);
      for(var i = 0; i < 5; i++) {
        var div = document.createElement('div');
        div.className = 'grid-gallery__item';
        document.body.appendChild(div);
      }
      items = document.querySelectorAll('.grid-gallery__item');

      expect(x._checkItems(items).length).toBe(5);
      expect(x._checkItems(items)[0].className).toBe('grid-gallery__item');
      expect(x._checkItems(items)[0].dataset.grid).toBeUndefined();
    });

    it('Private _calWhiteSpace: Should return whitespace after calc max number of items', function() {
      x = new GridGallery(container, options);
      expect(x._calWhiteSpace(600, 180)).toEqual(60);
    });

    it('Private _calWhiteSpace: Should return whitespace = zero', function() {
      x = new GridGallery(container, options);
      expect(x._calWhiteSpace(600, 200)).toEqual(0);
    });

    it('Private _disableAll: Should return array of element each of which has data-grid = \'\'', function() {
      x = new GridGallery(container, options);
      expect(x._disableAll(x.element.querySelectorAll('.grid-gallery__item'))).toEqual(jasmine.any(Array));
      expect(x._disableAll(x.element.querySelectorAll('.grid-gallery__item'))[0].dataset.grid).toEqual('');
      expect(x._disableAll(x.element.querySelectorAll('.grid-gallery__item')).length).toEqual(5);
    });
  });
});
<commit_msg>change default options 'remove lightBox option'<commit_after>
describe('GridGallery', function(){
  var x, options = { direction: 'left' };
  var container;
  document.body.className = 'grid-gallery__example';

  beforeEach(function() {
    var wrapper = document.createElement('div');
    wrapper.className = 'container';

    container = document.createElement('div');
    container.className = 'grid-gallery';

    for (var i = 0; i < 5; i++) {
      var element = document.createElement('div');
      var p = document.createElement('p');
      p.textContent = 'lorem nav of items in grid (disable/enable) depending on initialization';
      element.className = 'grid-gallery__item grid-gallery__dummy';
      element.appendChild(p);
      container.appendChild(element);
    }

    wrapper.appendChild(container);
    document.body.appendChild(wrapper);

  });

  afterEach(function(){
    var oldContainer = document.querySelector('.container');
    oldContainer.parentNode.removeChild(oldContainer);
  });

  describe('GridGallery initialization', function(){

    it('Global must have the GridGallery as a property', function() {
      x = new GridGallery(container, options);
     expect(GridGallery).not.toBeUndefined();
    });

    it('should manipulate the default options to the created instance', function() {
      var div = document.createElement('div');
      var childs = document.createElement('div');
      childs.className = 'grid-gallery__item';
      div.appendChild(childs);
      var y = new GridGallery( div, {});
      expect(y.options).toEqual({direction: 'left'});
    });

    it('should extend the default options and icludes the new one', function() {
      options.direction = 'right';
      x = new GridGallery(container, options);
      expect(x.options).toEqual({direction: 'right'});
    });

    it('should store the container as a property', function() {
      x = new GridGallery(container, options);
      expect(x.element).toEqual(container);
    });

    it('should have method init', function() {
      x = new GridGallery(container, options);
      expect(x.init).toBeDefined();
      expect(typeof x.init).toBe('function');
    });
  })

  describe('init() method', function() {
    it('should update the element and its childrens', function(){
      x = new GridGallery(container, options)
      expect(document.querySelector('.grid-gallery').offsetHeight).not.toBeLessThan(200);
      expect(document.querySelector('.grid-gallery__item').dataset.grid).toBe('true');
    });

    it('should update children positions', function() {
      var items;
      x = new GridGallery(container, options);

      items = document.body.querySelectorAll('.grid-gallery__item');
      for(var i = 0; i < items.length; i++) {
        expect(items[i].style.left).not.toBe('');
      }
    });
  });

  describe('update() method', function(){
    it('should be called on window resize', function(done){
      var event;
      x = new GridGallery(container, options);
      spyOn(x, 'update');

      event = document.createEvent('HTMLEvents');
      event.initEvent('resize', true, false);
      window.dispatchEvent(event);

      setTimeout(function(){
        expect(x.update).toHaveBeenCalled();
        done();
      }, 300);
    });

    it('should enable items and reposition it', function(done) {
      var event;
      x = new GridGallery(container, options);
      event = document.createEvent('HTMLEvents');
      event.initEvent('resize', true, false);
      window.dispatchEvent(event);

      setTimeout(function(){
        var item = x.element.querySelector('.grid-gallery__item');
        expect(item.dataset.grid).toEqual('true');
        done();
      }, 300);
    });

    it('should update positions if DOM changed using observer', function(done){
      var x = new GridGallery(container, options);
      spyOn(x, 'update');
      var div = document.createElement('div');
      var child = document.createElement('div');
      div.id = "dummyId";
      div.className = ['grid-gallery__item'];
      child.className = ' grid-gallery__dummy';
      div.appendChild(child);
      var cont = document.querySelector('.grid-gallery');
      cont.appendChild(div);

      setTimeout(function(){
        expect(document.getElementById('dummyId').dataset.grid).toEqual('true');
        expect(document.getElementById('dummyId').offsetTop).not.toBeLessThan(100);
        done();
      }, 300);
    });

    it('should update positions if DOM changed using EventListener', function(done){
      window.MutationObserver = undefined;
      window.WebKitMutationObserver = undefined;
      var x = new GridGallery(container, options);
      spyOn(x, 'update');
      var div = document.createElement('div');
      var child = document.createElement('div');
      div.id = "dummyId2";
      div.className = ['grid-gallery__item'];
      child.className = ' grid-gallery__dummy';
      div.appendChild(child);
      var cont = document.querySelector('.grid-gallery');
      cont.appendChild(div);

      setTimeout(function(){
        expect(document.getElementById('dummyId2').dataset.grid).toEqual('true');
        expect(document.getElementById('dummyId2').offsetTop).not.toBeLessThan(100);
        done();
      }, 300);
    });
  });

  describe('Private Methods', function() {
    it('Private _checkItems: should be defined', function() {
      x = new GridGallery(container, options);
      expect(x._checkItems).toBeDefined();
    });

    it('Private _checkItems: should return Array of not Enabled items', function() {
      var items;
      x = new GridGallery(container, options);
      for(var i = 0; i < 5; i++) {
        var div = document.createElement('div');
        div.className = 'grid-gallery__item';
        document.body.appendChild(div);
      }
      items = document.querySelectorAll('.grid-gallery__item');

      expect(x._checkItems(items).length).toBe(5);
      expect(x._checkItems(items)[0].className).toBe('grid-gallery__item');
      expect(x._checkItems(items)[0].dataset.grid).toBeUndefined();
    });

    it('Private _calWhiteSpace: Should return whitespace after calc max number of items', function() {
      x = new GridGallery(container, options);
      expect(x._calWhiteSpace(600, 180)).toEqual(60);
    });

    it('Private _calWhiteSpace: Should return whitespace = zero', function() {
      x = new GridGallery(container, options);
      expect(x._calWhiteSpace(600, 200)).toEqual(0);
    });

    it('Private _disableAll: Should return array of element each of which has data-grid = \'\'', function() {
      x = new GridGallery(container, options);
      expect(x._disableAll(x.element.querySelectorAll('.grid-gallery__item'))).toEqual(jasmine.any(Array));
      expect(x._disableAll(x.element.querySelectorAll('.grid-gallery__item'))[0].dataset.grid).toEqual('');
      expect(x._disableAll(x.element.querySelectorAll('.grid-gallery__item')).length).toEqual(5);
    });
  });
});
<|endoftext|>


<commit_before>module.exports = CSGOGSI;

var http = require('http');
var express = require('express');
var bodyParser = require('body-parser');

require('util').inherits(CSGOGSI, require('events').EventEmitter);

var app = express();
var server = http.createServer(app);

app.use( bodyParser.json());       // to support JSON-encoded bodies
app.use(bodyParser.urlencoded({     // to support URL-encoded bodies
    extended: true
}));

function CSGOGSI() {
    var self = this;
    require('events').EventEmitter.call(this);
    server.listen(process.env.PORT || 3000, process.env.IP || "0.0.0.0", function() {
        var addr = server.address();
        console.log("CSGO GSI server listening on", addr.address + ":" + addr.port);
    });

    app.post('/', function (req, res) {
        if (typeof req.body !== 'undefined') {
            self.emit('all', req.body);
            self.process(req.body);
        }
    });

    this._isBombPlanted = false;
    this._c4Interval;
}

CSGOGSI.prototype.process = function(data) {
    var self = this;
    if (typeof data.map !== 'undefined') {
        this.emit('gameMap', data.map.name);
        this.emit('gamePhase', data.map.phase); //warmup etc
        this.emit('gameRounds', data.map.round);
        this.emit('gameCTscore', data.map.team_ct_score);
        this.emit('gameTscore', data.map.team_t_score);
    }

    if (typeof data.player !== 'undefined') {
        this.emit('player', data.player);
    }

    if (typeof data.round !== 'undefined') {

        var maxTime = 0;
        this.emit('roundPhase', data.round.phase);
        switch(data.round.phase) {
            case 'live':
            maxTime = 115;
            break;
            case 'freezetime':
            maxTime = 15;
            break;
            case 'over':
            if (this._isBombPlanted) {
                this._isBombPlanted = false;
                clearInterval(this._c4Interval);
            }
            this.emit('roundWinTeam', data.round.win_team);
            break;
        }

        if (typeof data.round.bomb !== 'undefined') {
            // exploded, planted, defused
            this.emit('bombState', data.round.bomb);
            switch(data.round.bomb) {
                case 'planted':
                if (!this._isBombPlanted) {
                    this._isBombPlanted = true;
                    var timeleft = 40 - (new Date().getTime() / 1000 - data.provider.timestamp);
                    this.emit('bombTimeStart', timeleft);
                    this.c4Countdown(timeleft);
                }
                break;

                case 'defused':
                case 'exploded':
                this._isBombPlanted = false;
                clearInterval(this._c4Interval);
                break;
            }
        }
    }
};

CSGOGSI.prototype.c4Countdown = function(time) {
    var self = this;
    this._c4Interval = setInterval(function() {
        time = time - 1;
        if (time <= 0) {
            clearInterval(self._c4Interval);
            //counter ended, do something here
            self._isBombPlanted = false;
            return;
        }
        self.emit('bombTimeLeft', time);
    }, 1000);
};
<commit_msg>Formatting..<commit_after>module.exports = CSGOGSI;

var http = require('http');
var express = require('express');
var bodyParser = require('body-parser');

require('util').inherits(CSGOGSI, require('events').EventEmitter);

var app = express();
var server = http.createServer(app);

app.use( bodyParser.json());          // to support JSON-encoded bodies
app.use(bodyParser.urlencoded({       // to support URL-encoded bodies
    extended: true
}));

function CSGOGSI() {
    var self = this;
    require('events').EventEmitter.call(this);
    server.listen(process.env.PORT || 3000, process.env.IP || "0.0.0.0", function() {
        var addr = server.address();
        console.log("CSGO GSI server listening on", addr.address + ":" + addr.port);
    });

    app.post('/', function (req, res) {
        if (typeof req.body !== 'undefined') {
            self.emit('all', req.body);
            self.process(req.body);
        }
    });

    this._isBombPlanted = false;
    this._c4Interval;
}

CSGOGSI.prototype.process = function(data) {
    var self = this;
    if (typeof data.map !== 'undefined') {
        this.emit('gameMap', data.map.name);
        this.emit('gamePhase', data.map.phase); //warmup etc
        this.emit('gameRounds', data.map.round);
        this.emit('gameCTscore', data.map.team_ct_score);
        this.emit('gameTscore', data.map.team_t_score);
    }

    if (typeof data.player !== 'undefined') {
        this.emit('player', data.player);
    }

    if (typeof data.round !== 'undefined') {

        var maxTime = 0;
        this.emit('roundPhase', data.round.phase);
        switch(data.round.phase) {
            case 'live':
            maxTime = 115;
            break;
            case 'freezetime':
            maxTime = 15;
            break;
            case 'over':
            if (this._isBombPlanted) {
                this._isBombPlanted = false;
                clearInterval(this._c4Interval);
            }
            this.emit('roundWinTeam', data.round.win_team);
            break;
        }

        if (typeof data.round.bomb !== 'undefined') {
            // exploded, planted, defused
            this.emit('bombState', data.round.bomb);
            switch(data.round.bomb) {
                case 'planted':
                if (!this._isBombPlanted) {
                    this._isBombPlanted = true;
                    var timeleft = 40 - (new Date().getTime() / 1000 - data.provider.timestamp);
                    this.emit('bombTimeStart', timeleft);
                    this.c4Countdown(timeleft);
                }
                break;
                case 'defused':
                case 'exploded':
                this._isBombPlanted = false;
                clearInterval(this._c4Interval);
                break;
            }
        }
    }
};

CSGOGSI.prototype.c4Countdown = function(time) {
    var self = this;
    this._c4Interval = setInterval(function() {
        time = time - 1;
        if (time <= 0) {
            clearInterval(self._c4Interval);
            //counter ended, do something here
            self._isBombPlanted = false;
            return;
        }
        self.emit('bombTimeLeft', time);
    }, 1000);
};
<|endoftext|>


<commit_before>'use strict';

var $grid;

// Initializes the grid with packery
function initializeGrid() {
    $grid = $('#content').packery({
        itemSelector: '.component',
        gutter: 15,
        columnWidth: 25,
        rawHeight: 25
    });
}

// Adds a component in the packery grid
function addComponent(content) {
    var $item = $(content);
    $grid.append($item).packery('appended', $item);
    $grid.packery('bindUIDraggableEvents', $item.draggable());
}

// Removes a component in the packery grid
function removeComponent(id) {
    $grid.packery('remove', $(id));
}

module.exports = {
    initializeGrid: initializeGrid,
    addComponent: addComponent,
    removeComponent: removeComponent
};<commit_msg>Adjust grid<commit_after>'use strict';

var $grid;

// Initializes the grid with packery
function initializeGrid() {
    $grid = $('#content').packery({
        itemSelector: '.component',
        gutter: 15,
        columnWidth: 15,
        rawHeight: 15
    });
}

// Adds a component in the packery grid
function addComponent(content) {
    var $item = $(content);
    $grid.append($item).packery('appended', $item);
    $grid.packery('bindUIDraggableEvents', $item.draggable());
}

// Removes a component in the packery grid
function removeComponent(id) {
    $grid.packery('remove', $(id));
}

module.exports = {
    initializeGrid: initializeGrid,
    addComponent: addComponent,
    removeComponent: removeComponent
};<|endoftext|>


<commit_before>const axios = require('axios');

const createAlert = require('./src/alert');
const createJam = require('./src/jam');

const serverList = ['rtserver', 'row-rtserver', 'il-rtserver'];

function notificationUrl(server, left, right, top, bottom) {
    return axios.get(`https://www.waze.com/${server}/web/TGeoRSS`, {
        params: {
            left,
            right,
            top,
            bottom
        }
    });
}

function getTraffic(segment) {
    if (!segment.top && !segment.right && !segment.bottom && !segment.left) return Promise.reject('Please provide top, right, bottom, left');
    return Promise.all(serverList.map(server => notificationUrl(server, segment.left, segment.right, segment.top, segment.bottom)))
        .then(responses => responses
            .map(response => {
                const obj = {
                    alerts: [],
                    jams: [],
                };
                if (response.data.alerts) {
                    obj.alerts = response.data.alerts.map(alert => createAlert(alert))
                }
                if (response.data.jams) {
                    obj.jams = response.data.jams.map(alert => createJam(alert))
                }
                return obj;
            })
            .reduce((prev, curr) => {
                return Object.assign(prev, curr);
            }), {});
}

module.exports = {
    getTraffic,
};
<commit_msg>fixing reducer part, because assigning object no concatenating array values (#5)<commit_after>const axios = require('axios');

const createAlert = require('./src/alert');
const createJam = require('./src/jam');

const serverList = ['rtserver', 'row-rtserver', 'il-rtserver'];

function notificationUrl(server, left, right, top, bottom) {
    return axios.get(`https://www.waze.com/${server}/web/TGeoRSS`, {
        params: {
            left,
            right,
            top,
            bottom
        }
    });
}

function getTraffic(segment) {
    if (!segment.top && !segment.right && !segment.bottom && !segment.left) return Promise.reject('Please provide top, right, bottom, left');
    return Promise.all(serverList.map(server => notificationUrl(server, segment.left, segment.right, segment.top, segment.bottom)))
        .then(responses => responses
            .map(response => {
                const obj = {
                    alerts: [],
                    jams: [],
                };
                if (response.data.alerts) {
                    obj.alerts = response.data.alerts.map(alert => createAlert(alert))
                }
                if (response.data.jams) {
                    obj.jams = response.data.jams.map(alert => createJam(alert))
                }
                return obj;
            })
            .reduce((prev, curr) => {
                let newObj = {};
                newObj.alerts = prev.alerts.concat(curr.alerts);
                newObj.jams = prev.jams.concat(curr.jams);
                return newObj;
            }), {alerts:[],jams:[]});
}

module.exports = {
    getTraffic,
};
<|endoftext|>


<commit_before>import { Settings } from "api"
import CustomDateInput from "components/CustomDateInput"
import * as FieldHelper from "components/FieldHelper"
import Fieldset from "components/Fieldset"
import LikertScale from "components/graphs/LikertScale"
import Model, {
  CUSTOM_FIELD_TYPE,
  createYupObjectShape
} from "components/Model"
import { FastField, FieldArray } from "formik"
import { JSONPath } from "jsonpath-plus"
import _cloneDeep from "lodash/cloneDeep"
import _isEmpty from "lodash/isEmpty"
import _isEqualWith from "lodash/isEqualWith"
import _set from "lodash/set"
import _upperFirst from "lodash/upperFirst"
import moment from "moment"
import PropTypes from "prop-types"
import React, { useEffect, useMemo, useRef, useState } from "react"
import { Button, HelpBlock } from "react-bootstrap"
import REMOVE_ICON from "resources/delete.png"
import utils from "utils"

const WIDGETS = {
  likertScale: LikertScale
}
const RENDERERS = {}

const DEFAULT_CUSTOM_FIELDS_PREFIX = "formCustomFields"

const SpecialField = fieldProps => {
  const { widget, ...otherFieldProps } = fieldProps
  const SpecialFieldWidget = WIDGETS[widget]
  return (
    <FastField
      component={FieldHelper.SpecialField}
      widget={<SpecialFieldWidget />}
      {...otherFieldProps}
    />
  )
}

const ReadonlySpecialField = fieldProps =>
  SpecialField({ ...fieldProps, readonly: true })

const TextField = fieldProps => {
  const { onChange, onBlur, ...otherFieldProps } = fieldProps
  return <FastField component={FieldHelper.InputField} {...otherFieldProps} />
}

const ReadonlyTextField = fieldProps => {
  const { name, label } = fieldProps
  return (
    <FastField
      name={name}
      label={label}
      component={FieldHelper.ReadonlyField}
    />
  )
}

const DateField = fieldProps => {
  const { name, withTime, ...otherFieldProps } = fieldProps
  return (
    <FastField
      name={name}
      component={FieldHelper.SpecialField}
      widget={<CustomDateInput id={name} withTime={withTime} />}
      {...otherFieldProps}
    />
  )
}

const ReadonlyDateField = fieldProps => {
  const { name, label, withTime } = fieldProps
  return (
    <FastField
      name={name}
      label={label}
      component={FieldHelper.ReadonlyField}
      humanValue={fieldVal =>
        fieldVal &&
        moment(fieldVal).format(
          withTime
            ? Settings.dateFormats.forms.displayShort.withTime
            : Settings.dateFormats.forms.displayShort.date
        )}
    />
  )
}

const DateTimeField = props => <DateField {...props} withTime />

const ReadonlyDateTimeField = props => <ReadonlyDateField {...props} withTime />

const EnumField = fieldProps => {
  const { choices, renderer, ...otherFieldProps } = fieldProps
  return (
    <FastField
      buttons={FieldHelper.customEnumButtons(choices)}
      component={RENDERERS[renderer] || FieldHelper.RadioButtonToggleGroup}
      {...otherFieldProps}
    />
  )
}

const enumHumanValue = (choices, fieldVal) => {
  if (Array.isArray(fieldVal)) {
    return fieldVal && fieldVal.map(k => choices[k].label).join(", ")
  } else {
    return fieldVal && choices[fieldVal].label
  }
}

const ReadonlyEnumField = fieldProps => {
  const { name, label, choices } = fieldProps
  return (
    <FastField
      name={name}
      label={label}
      component={FieldHelper.ReadonlyField}
      humanValue={fieldVal => enumHumanValue(choices, fieldVal)}
    />
  )
}

const EnumSetField = fieldProps => {
  const { choices, renderer, ...otherFieldProps } = fieldProps
  return (
    <FastField
      buttons={FieldHelper.customEnumButtons(choices)}
      component={RENDERERS[renderer] || FieldHelper.CheckboxButtonToggleGroup}
      {...otherFieldProps}
    />
  )
}

const ReadonlyEnumSetField = fieldProps => {
  const { name, label, choices } = fieldProps
  return (
    <FastField
      name={name}
      label={label}
      component={FieldHelper.ReadonlyField}
      humanValue={fieldVal => enumHumanValue(choices, fieldVal)}
    />
  )
}

const ArrayOfObjectsField = fieldProps => {
  const {
    name,
    fieldConfig,
    formikProps,
    invisibleFields,
    updateInvisibleFields
  } = fieldProps
  const nameKeys = name.split(".")
  const value = nameKeys.reduce(
    (v, key) => (v && v[key] ? v[key] : []),
    formikProps.values
  )
  const fieldsetTitle = fieldConfig.label || ""
  const addButtonLabel = fieldConfig.addButtonLabel || "Add a new item"
  const objSchema = createYupObjectShape(fieldConfig.objectFields)
  const objDefault = {}
  Model.fillObject(objDefault, objSchema)

  return (
    <Fieldset title={fieldsetTitle}>
      <FieldArray
        name={name}
        render={arrayHelpers => (

            <Button
              className="pull-right"
              onClick={() => addObject(objDefault, arrayHelpers)}
              bsStyle="primary"
              id="addObjectButton"
            >
              {addButtonLabel}
            </Button>
            {value.map((obj, index) => (
              <ArrayObject
                key={index}
                fieldName={name}
                fieldConfig={fieldConfig}
                formikProps={formikProps}
                invisibleFields={invisibleFields}
                updateInvisibleFields={updateInvisibleFields}
                arrayHelpers={arrayHelpers}
                index={index}
              />
            ))}

        )}
      />
    </Fieldset>
  )
}

const ArrayObject = ({
  fieldName,
  fieldConfig,
  formikProps,
  invisibleFields,
  updateInvisibleFields,
  arrayHelpers,
  index
}) => {
  const objLabel = _upperFirst(fieldConfig.objectLabel || "item")
  return (
    <Fieldset title={`${objLabel} ${index + 1}`}>
      <Button
        className="pull-right"
        title={`Remove this ${objLabel}`}
        onClick={() => arrayHelpers.remove(index)}
      >
        <img src={REMOVE_ICON} height={14} alt={`Remove this ${objLabel}`} />
      </Button>
      <CustomFields
        fieldsConfig={fieldConfig.objectFields}
        formikProps={formikProps}
        invisibleFields={invisibleFields}
        updateInvisibleFields={updateInvisibleFields}
        fieldNamePrefix={`${fieldName}.${index}`}
      />
    </Fieldset>
  )
}
ArrayObject.propTypes = {
  fieldName: PropTypes.string.isRequired,
  fieldConfig: PropTypes.object.isRequired,
  formikProps: PropTypes.object.isRequired,
  invisibleFields: PropTypes.array.isRequired,
  updateInvisibleFields: PropTypes.func.isRequired,
  arrayHelpers: PropTypes.object.isRequired,
  index: PropTypes.number.isRequired
}

const addObject = (objDefault, arrayHelpers) => {
  arrayHelpers.push(objDefault)
}

const ReadonlyArrayOfObjectsField = fieldProps => {
  const { name, fieldConfig, formikProps } = fieldProps
  const nameKeys = name.split(".")
  const value = nameKeys.reduce(
    (v, key) => (v && v[key] ? v[key] : []),
    formikProps.values
  )
  const fieldsetTitle = fieldConfig.label || ""
  return (
    <Fieldset title={fieldsetTitle}>
      <FieldArray
        name={name}
        render={arrayHelpers => (

            {value.map((obj, index) => (
              <ReadonlyArrayObject
                key={index}
                fieldName={name}
                fieldConfig={fieldConfig}
                formikProps={formikProps}
                index={index}
              />
            ))}

        )}
      />
    </Fieldset>
  )
}

const ReadonlyArrayObject = ({
  fieldName,
  fieldConfig,
  formikProps,
  index
}) => {
  const objLabel = _upperFirst(fieldConfig.objectLabel || "item")
  return (
    <Fieldset title={`${objLabel} ${index + 1}`}>
      <ReadonlyCustomFields
        fieldsConfig={fieldConfig.objectFields}
        formikProps={formikProps}
        fieldNamePrefix={`${fieldName}.${index}`}
      />
    </Fieldset>
  )
}
ReadonlyArrayObject.propTypes = {
  fieldName: PropTypes.string.isRequired,
  fieldConfig: PropTypes.object.isRequired,
  formikProps: PropTypes.object.isRequired,
  index: PropTypes.number.isRequired
}

const FIELD_COMPONENTS = {
  [CUSTOM_FIELD_TYPE.TEXT]: TextField,
  [CUSTOM_FIELD_TYPE.NUMBER]: TextField,
  [CUSTOM_FIELD_TYPE.DATE]: DateField,
  [CUSTOM_FIELD_TYPE.DATETIME]: DateTimeField,
  [CUSTOM_FIELD_TYPE.ENUM]: EnumField,
  [CUSTOM_FIELD_TYPE.ENUMSET]: EnumSetField,
  [CUSTOM_FIELD_TYPE.ARRAY_OF_OBJECTS]: ArrayOfObjectsField,
  [CUSTOM_FIELD_TYPE.SPECIAL_FIELD]: SpecialField
}

function getInvisibleFields(
  invisibleFields,
  fieldsConfig,
  fieldNamePrefix,
  formikValues
) {
  const prevInvisibleFields = _cloneDeep(invisibleFields)
  const turnedInvisible = []
  const turnedVisible = []
  let curInvisibleFields = []
  Object.keys(fieldsConfig).forEach(key => {
    const fieldConfig = fieldsConfig[key]
    const fieldName = `${fieldNamePrefix}.${key}`
    const isVisible =
      !fieldConfig.visibleWhen ||
      (fieldConfig.visibleWhen &&
        !_isEmpty(JSONPath(fieldConfig.visibleWhen, formikValues)))
    if (!isVisible && !prevInvisibleFields.includes(fieldName)) {
      turnedInvisible.push(fieldName)
    } else if (isVisible && prevInvisibleFields.includes(fieldName)) {
      turnedVisible.push(fieldName)
    }
  })
  if (turnedVisible.length || turnedInvisible.length) {
    curInvisibleFields = prevInvisibleFields.filter(
      x => !turnedVisible.includes(x)
    )
    turnedInvisible.forEach(x => curInvisibleFields.push(x))
    return curInvisibleFields
  }
  return invisibleFields
}

export const CustomFieldsContainer = props => {
  const { fieldNamePrefix, formikProps } = props
  const [invisibleFields, setInvisibleFields] = useState([])
  const { setFieldValue } = formikProps
  const invisibleFieldsFieldName = `${fieldNamePrefix}.invisibleCustomFields`
  useEffect(() => {
    setFieldValue(invisibleFieldsFieldName, invisibleFields)
  }, [invisibleFieldsFieldName, invisibleFields, setFieldValue])

  return (
    <>
      <FastField
        type="text"
        value=""
        name={invisibleFieldsFieldName}
        className="hidden"
      />
      <CustomFields
        invisibleFields={invisibleFields}
        updateInvisibleFields={setInvisibleFields}
        {...props}
      />
    </>
  )
}
CustomFieldsContainer.propTypes = {
  fieldsConfig: PropTypes.object,
  formikProps: PropTypes.object,
  fieldNamePrefix: PropTypes.string.isRequired
}
CustomFieldsContainer.defaultProps = {
  fieldNamePrefix: DEFAULT_CUSTOM_FIELDS_PREFIX
}

const CustomField = ({
  fieldConfig,
  fieldName,
  formikProps,
  invisibleFields,
  updateInvisibleFields
}) => {
  const {
    type,
    helpText,
    validations,
    visibleWhen,
    ...fieldProps
  } = fieldConfig
  const { setFieldValue } = formikProps
  const handleChange = useMemo(() => value => setFieldValue(fieldName, value), [
    setFieldValue,
    fieldName
  ])
  const FieldComponent = FIELD_COMPONENTS[type]
  const extraProps = useMemo(
    () =>
      type !== CUSTOM_FIELD_TYPE.ARRAY_OF_OBJECTS
        ? {}
        : {
          fieldConfig,
          formikProps,
          invisibleFields,
          updateInvisibleFields
        },
    [fieldConfig, formikProps, invisibleFields, type, updateInvisibleFields]
  )
  return (
    <FieldComponent
      name={fieldName}
      onChange={handleChange}
      {...fieldProps}
      {...extraProps}
    >
      {helpText && (
        <HelpBlock>
          {helpText}
        </HelpBlock>
      )}
    </FieldComponent>
  )
}
CustomField.propTypes = {
  fieldConfig: PropTypes.object,
  fieldName: PropTypes.string.isRequired,
  formikProps: PropTypes.object,
  invisibleFields: PropTypes.array,
  updateInvisibleFields: PropTypes.func
}

const CustomFields = ({
  fieldsConfig,
  formikProps,
  fieldNamePrefix,
  invisibleFields,
  updateInvisibleFields
}) => {
  const formikValues = formikProps.values

  const latestInvisibleFieldsProp = useRef(invisibleFields)
  const invisibleFieldsPropUnchanged = _isEqualWith(
    latestInvisibleFieldsProp.current,
    invisibleFields,
    utils.treatFunctionsAsEqual
  )

  const curInvisibleFields = useMemo(
    () =>
      getInvisibleFields(
        invisibleFields,
        fieldsConfig,
        fieldNamePrefix,
        formikValues
      ),
    [invisibleFields, fieldsConfig, fieldNamePrefix, formikValues]
  )
  const invisibleFieldsUnchanged = _isEqualWith(
    latestInvisibleFieldsProp.current,
    curInvisibleFields,
    utils.treatFunctionsAsEqual
  )

  useEffect(() => {
    if (!invisibleFieldsPropUnchanged) {
      latestInvisibleFieldsProp.current = invisibleFields
    }
  }, [invisibleFieldsPropUnchanged, invisibleFields])

  useEffect(() => {
    if (!invisibleFieldsUnchanged) {
      updateInvisibleFields(curInvisibleFields)
    }
  }, [invisibleFieldsUnchanged, curInvisibleFields, updateInvisibleFields])

  return (
    <>
      {Object.keys(fieldsConfig).map(key => {
        const fieldConfig = fieldsConfig[key]
        const fieldName = `${fieldNamePrefix}.${key}`
        return invisibleFields.includes(fieldName) ? null : (
          <CustomField
            key={key}
            fieldConfig={fieldConfig}
            fieldName={fieldName}
            formikProps={formikProps}
            invisibleFields={invisibleFields}
            updateInvisibleFields={updateInvisibleFields}
          />
        )
      })}
    </>
  )
}
CustomFields.propTypes = {
  fieldsConfig: PropTypes.object,
  formikProps: PropTypes.object,
  fieldNamePrefix: PropTypes.string.isRequired,
  invisibleFields: PropTypes.array,
  updateInvisibleFields: PropTypes.func
}
CustomFields.defaultProps = {
  fieldNamePrefix: DEFAULT_CUSTOM_FIELDS_PREFIX
}

const READONLY_FIELD_COMPONENTS = {
  [CUSTOM_FIELD_TYPE.TEXT]: ReadonlyTextField,
  [CUSTOM_FIELD_TYPE.NUMBER]: ReadonlyTextField,
  [CUSTOM_FIELD_TYPE.DATE]: ReadonlyDateField,
  [CUSTOM_FIELD_TYPE.DATETIME]: ReadonlyDateTimeField,
  [CUSTOM_FIELD_TYPE.ENUM]: ReadonlyEnumField,
  [CUSTOM_FIELD_TYPE.ENUMSET]: ReadonlyEnumSetField,
  [CUSTOM_FIELD_TYPE.ARRAY_OF_OBJECTS]: ReadonlyArrayOfObjectsField,
  [CUSTOM_FIELD_TYPE.SPECIAL_FIELD]: ReadonlySpecialField
}

export const ReadonlyCustomFields = ({
  fieldsConfig,
  formikProps,
  fieldNamePrefix
}) => {
  return (
    <>
      {Object.keys(fieldsConfig).map(key => {
        const fieldConfig = fieldsConfig[key]
        const {
          type,
          placeholder,
          helpText,
          validations,
          visibleWhen,
          objectFields,
          ...fieldProps
        } = fieldConfig
        let extraProps = {}
        if (type === CUSTOM_FIELD_TYPE.ARRAY_OF_OBJECTS) {
          extraProps = {
            fieldConfig,
            formikProps
          }
        }
        const FieldComponent = READONLY_FIELD_COMPONENTS[type]
        return (
          <FieldComponent
            key={key}
            name={`${fieldNamePrefix}.${key}`}
            {...fieldProps}
            {...extraProps}
          />
        )
      })}
    </>
  )
}
ReadonlyCustomFields.propTypes = {
  fieldsConfig: PropTypes.object,
  formikProps: PropTypes.object,
  fieldNamePrefix: PropTypes.string.isRequired
}
ReadonlyCustomFields.defaultProps = {
  fieldNamePrefix: DEFAULT_CUSTOM_FIELDS_PREFIX
}

// customFields should contain the JSON of all the visible custom fields
export const customFieldsJSONString = values => {
  const clonedValues = _cloneDeep(values)
  if (values.formCustomFields.invisibleCustomFields) {
    clonedValues.formCustomFields.invisibleCustomFields.forEach(f => {
      _set(clonedValues, f.split("."), undefined)
    })
  }
  return JSON.stringify(clonedValues.formCustomFields)
}
<commit_msg>NCI-Agency/anet#2352: Memoize some array of objects calculations<commit_after>import { Settings } from "api"
import CustomDateInput from "components/CustomDateInput"
import * as FieldHelper from "components/FieldHelper"
import Fieldset from "components/Fieldset"
import LikertScale from "components/graphs/LikertScale"
import Model, {
  CUSTOM_FIELD_TYPE,
  createYupObjectShape
} from "components/Model"
import { FastField, FieldArray } from "formik"
import { JSONPath } from "jsonpath-plus"
import _cloneDeep from "lodash/cloneDeep"
import _isEmpty from "lodash/isEmpty"
import _isEqualWith from "lodash/isEqualWith"
import _set from "lodash/set"
import _upperFirst from "lodash/upperFirst"
import moment from "moment"
import PropTypes from "prop-types"
import React, { useEffect, useMemo, useRef, useState } from "react"
import { Button, HelpBlock } from "react-bootstrap"
import REMOVE_ICON from "resources/delete.png"
import utils from "utils"

const WIDGETS = {
  likertScale: LikertScale
}
const RENDERERS = {}

const DEFAULT_CUSTOM_FIELDS_PREFIX = "formCustomFields"

const SpecialField = fieldProps => {
  const { widget, ...otherFieldProps } = fieldProps
  const SpecialFieldWidget = WIDGETS[widget]
  return (
    <FastField
      component={FieldHelper.SpecialField}
      widget={<SpecialFieldWidget />}
      {...otherFieldProps}
    />
  )
}

const ReadonlySpecialField = fieldProps =>
  SpecialField({ ...fieldProps, readonly: true })

const TextField = fieldProps => {
  const { onChange, onBlur, ...otherFieldProps } = fieldProps
  return <FastField component={FieldHelper.InputField} {...otherFieldProps} />
}

const ReadonlyTextField = fieldProps => {
  const { name, label } = fieldProps
  return (
    <FastField
      name={name}
      label={label}
      component={FieldHelper.ReadonlyField}
    />
  )
}

const DateField = fieldProps => {
  const { name, withTime, ...otherFieldProps } = fieldProps
  return (
    <FastField
      name={name}
      component={FieldHelper.SpecialField}
      widget={<CustomDateInput id={name} withTime={withTime} />}
      {...otherFieldProps}
    />
  )
}

const ReadonlyDateField = fieldProps => {
  const { name, label, withTime } = fieldProps
  return (
    <FastField
      name={name}
      label={label}
      component={FieldHelper.ReadonlyField}
      humanValue={fieldVal =>
        fieldVal &&
        moment(fieldVal).format(
          withTime
            ? Settings.dateFormats.forms.displayShort.withTime
            : Settings.dateFormats.forms.displayShort.date
        )}
    />
  )
}

const DateTimeField = props => <DateField {...props} withTime />

const ReadonlyDateTimeField = props => <ReadonlyDateField {...props} withTime />

const EnumField = fieldProps => {
  const { choices, renderer, ...otherFieldProps } = fieldProps
  return (
    <FastField
      buttons={FieldHelper.customEnumButtons(choices)}
      component={RENDERERS[renderer] || FieldHelper.RadioButtonToggleGroup}
      {...otherFieldProps}
    />
  )
}

const enumHumanValue = (choices, fieldVal) => {
  if (Array.isArray(fieldVal)) {
    return fieldVal && fieldVal.map(k => choices[k].label).join(", ")
  } else {
    return fieldVal && choices[fieldVal].label
  }
}

const ReadonlyEnumField = fieldProps => {
  const { name, label, choices } = fieldProps
  return (
    <FastField
      name={name}
      label={label}
      component={FieldHelper.ReadonlyField}
      humanValue={fieldVal => enumHumanValue(choices, fieldVal)}
    />
  )
}

const EnumSetField = fieldProps => {
  const { choices, renderer, ...otherFieldProps } = fieldProps
  return (
    <FastField
      buttons={FieldHelper.customEnumButtons(choices)}
      component={RENDERERS[renderer] || FieldHelper.CheckboxButtonToggleGroup}
      {...otherFieldProps}
    />
  )
}

const getArrayObjectValue = (values, fieldName) => {
  const nameKeys = fieldName.split(".")
  return nameKeys.reduce((v, key) => (v && v[key] ? v[key] : []), values)
}

const ArrayOfObjectsField = fieldProps => {
  const {
    name,
    fieldConfig,
    formikProps,
    invisibleFields,
    updateInvisibleFields
  } = fieldProps
  const value = useMemo(() => getArrayObjectValue(formikProps.values, name), [formikProps.values, name])
  const objDefault = useMemo(() => {
    const objDefault = {}
    const objSchema = createYupObjectShape(fieldConfig.objectFields)
    return Model.fillObject(objDefault, objSchema)
  }, [fieldConfig.objectFields])
  const fieldsetTitle = fieldConfig.label || ""
  const addButtonLabel = fieldConfig.addButtonLabel || "Add a new item"
  return (
    <Fieldset title={fieldsetTitle}>
      <FieldArray
        name={name}
        render={arrayHelpers => (

            <Button
              className="pull-right"
              onClick={() => addObject(objDefault, arrayHelpers)}
              bsStyle="primary"
              id="addObjectButton"
            >
              {addButtonLabel}
            </Button>
            {value.map((obj, index) => (
              <ArrayObject
                key={index}
                fieldName={name}
                fieldConfig={fieldConfig}
                formikProps={formikProps}
                invisibleFields={invisibleFields}
                updateInvisibleFields={updateInvisibleFields}
                arrayHelpers={arrayHelpers}
                index={index}
              />
            ))}

        )}
      />
    </Fieldset>
  )
}

const ArrayObject = ({
  fieldName,
  fieldConfig,
  formikProps,
  invisibleFields,
  updateInvisibleFields,
  arrayHelpers,
  index
}) => {
  const objLabel = _upperFirst(fieldConfig.objectLabel || "item")
  return (
    <Fieldset title={`${objLabel} ${index + 1}`}>
      <Button
        className="pull-right"
        title={`Remove this ${objLabel}`}
        onClick={() => arrayHelpers.remove(index)}
      >
        <img src={REMOVE_ICON} height={14} alt={`Remove this ${objLabel}`} />
      </Button>
      <CustomFields
        fieldsConfig={fieldConfig.objectFields}
        formikProps={formikProps}
        invisibleFields={invisibleFields}
        updateInvisibleFields={updateInvisibleFields}
        fieldNamePrefix={`${fieldName}.${index}`}
      />
    </Fieldset>
  )
}
ArrayObject.propTypes = {
  fieldName: PropTypes.string.isRequired,
  fieldConfig: PropTypes.object.isRequired,
  formikProps: PropTypes.object.isRequired,
  invisibleFields: PropTypes.array.isRequired,
  updateInvisibleFields: PropTypes.func.isRequired,
  arrayHelpers: PropTypes.object.isRequired,
  index: PropTypes.number.isRequired
}

const addObject = (objDefault, arrayHelpers) => {
  arrayHelpers.push(objDefault)
}

const ReadonlyArrayOfObjectsField = fieldProps => {
  const { name, fieldConfig, formikProps } = fieldProps
  const value = useMemo(() => getArrayObjectValue(formikProps.values, name), [formikProps.values, name])
  const fieldsetTitle = fieldConfig.label || ""
  return (
    <Fieldset title={fieldsetTitle}>
      <FieldArray
        name={name}
        render={arrayHelpers => (

            {value.map((obj, index) => (
              <ReadonlyArrayObject
                key={index}
                fieldName={name}
                fieldConfig={fieldConfig}
                formikProps={formikProps}
                index={index}
              />
            ))}

        )}
      />
    </Fieldset>
  )
}

const ReadonlyArrayObject = ({
  fieldName,
  fieldConfig,
  formikProps,
  index
}) => {
  const objLabel = _upperFirst(fieldConfig.objectLabel || "item")
  return (
    <Fieldset title={`${objLabel} ${index + 1}`}>
      <ReadonlyCustomFields
        fieldsConfig={fieldConfig.objectFields}
        formikProps={formikProps}
        fieldNamePrefix={`${fieldName}.${index}`}
      />
    </Fieldset>
  )
}
ReadonlyArrayObject.propTypes = {
  fieldName: PropTypes.string.isRequired,
  fieldConfig: PropTypes.object.isRequired,
  formikProps: PropTypes.object.isRequired,
  index: PropTypes.number.isRequired
}

const FIELD_COMPONENTS = {
  [CUSTOM_FIELD_TYPE.TEXT]: TextField,
  [CUSTOM_FIELD_TYPE.NUMBER]: TextField,
  [CUSTOM_FIELD_TYPE.DATE]: DateField,
  [CUSTOM_FIELD_TYPE.DATETIME]: DateTimeField,
  [CUSTOM_FIELD_TYPE.ENUM]: EnumField,
  [CUSTOM_FIELD_TYPE.ENUMSET]: EnumSetField,
  [CUSTOM_FIELD_TYPE.ARRAY_OF_OBJECTS]: ArrayOfObjectsField,
  [CUSTOM_FIELD_TYPE.SPECIAL_FIELD]: SpecialField
}

function getInvisibleFields(
  invisibleFields,
  fieldsConfig,
  fieldNamePrefix,
  formikValues
) {
  const prevInvisibleFields = _cloneDeep(invisibleFields)
  const turnedInvisible = []
  const turnedVisible = []
  let curInvisibleFields = []
  Object.keys(fieldsConfig).forEach(key => {
    const fieldConfig = fieldsConfig[key]
    const fieldName = `${fieldNamePrefix}.${key}`
    const isVisible =
      !fieldConfig.visibleWhen ||
      (fieldConfig.visibleWhen &&
        !_isEmpty(JSONPath(fieldConfig.visibleWhen, formikValues)))
    if (!isVisible && !prevInvisibleFields.includes(fieldName)) {
      turnedInvisible.push(fieldName)
    } else if (isVisible && prevInvisibleFields.includes(fieldName)) {
      turnedVisible.push(fieldName)
    }
  })
  if (turnedVisible.length || turnedInvisible.length) {
    curInvisibleFields = prevInvisibleFields.filter(
      x => !turnedVisible.includes(x)
    )
    turnedInvisible.forEach(x => curInvisibleFields.push(x))
    return curInvisibleFields
  }
  return invisibleFields
}

export const CustomFieldsContainer = props => {
  const { fieldNamePrefix, formikProps } = props
  const [invisibleFields, setInvisibleFields] = useState([])
  const { setFieldValue } = formikProps
  const invisibleFieldsFieldName = `${fieldNamePrefix}.invisibleCustomFields`
  useEffect(() => {
    setFieldValue(invisibleFieldsFieldName, invisibleFields)
  }, [invisibleFieldsFieldName, invisibleFields, setFieldValue])

  return (
    <>
      <FastField
        type="text"
        value=""
        name={invisibleFieldsFieldName}
        className="hidden"
      />
      <CustomFields
        invisibleFields={invisibleFields}
        updateInvisibleFields={setInvisibleFields}
        {...props}
      />
    </>
  )
}
CustomFieldsContainer.propTypes = {
  fieldsConfig: PropTypes.object,
  formikProps: PropTypes.object,
  fieldNamePrefix: PropTypes.string.isRequired
}
CustomFieldsContainer.defaultProps = {
  fieldNamePrefix: DEFAULT_CUSTOM_FIELDS_PREFIX
}

const CustomField = ({
  fieldConfig,
  fieldName,
  formikProps,
  invisibleFields,
  updateInvisibleFields
}) => {
  const {
    type,
    helpText,
    validations,
    visibleWhen,
    ...fieldProps
  } = fieldConfig
  const { setFieldValue } = formikProps
  const handleChange = useMemo(() => value => setFieldValue(fieldName, value), [
    setFieldValue,
    fieldName
  ])
  const FieldComponent = FIELD_COMPONENTS[type]
  const extraProps = useMemo(
    () =>
      type !== CUSTOM_FIELD_TYPE.ARRAY_OF_OBJECTS
        ? {}
        : {
          fieldConfig,
          formikProps,
          invisibleFields,
          updateInvisibleFields
        },
    [fieldConfig, formikProps, invisibleFields, type, updateInvisibleFields]
  )
  return (
    <FieldComponent
      name={fieldName}
      onChange={handleChange}
      {...fieldProps}
      {...extraProps}
    >
      {helpText && (
        <HelpBlock>
          {helpText}
        </HelpBlock>
      )}
    </FieldComponent>
  )
}
CustomField.propTypes = {
  fieldConfig: PropTypes.object,
  fieldName: PropTypes.string.isRequired,
  formikProps: PropTypes.object,
  invisibleFields: PropTypes.array,
  updateInvisibleFields: PropTypes.func
}

const CustomFields = ({
  fieldsConfig,
  formikProps,
  fieldNamePrefix,
  invisibleFields,
  updateInvisibleFields
}) => {
  const formikValues = formikProps.values

  const latestInvisibleFieldsProp = useRef(invisibleFields)
  const invisibleFieldsPropUnchanged = _isEqualWith(
    latestInvisibleFieldsProp.current,
    invisibleFields,
    utils.treatFunctionsAsEqual
  )

  const curInvisibleFields = useMemo(
    () =>
      getInvisibleFields(
        invisibleFields,
        fieldsConfig,
        fieldNamePrefix,
        formikValues
      ),
    [invisibleFields, fieldsConfig, fieldNamePrefix, formikValues]
  )
  const invisibleFieldsUnchanged = _isEqualWith(
    latestInvisibleFieldsProp.current,
    curInvisibleFields,
    utils.treatFunctionsAsEqual
  )

  useEffect(() => {
    if (!invisibleFieldsPropUnchanged) {
      latestInvisibleFieldsProp.current = invisibleFields
    }
  }, [invisibleFieldsPropUnchanged, invisibleFields])

  useEffect(() => {
    if (!invisibleFieldsUnchanged) {
      updateInvisibleFields(curInvisibleFields)
    }
  }, [invisibleFieldsUnchanged, curInvisibleFields, updateInvisibleFields])

  return (
    <>
      {Object.keys(fieldsConfig).map(key => {
        const fieldConfig = fieldsConfig[key]
        const fieldName = `${fieldNamePrefix}.${key}`
        return invisibleFields.includes(fieldName) ? null : (
          <CustomField
            key={key}
            fieldConfig={fieldConfig}
            fieldName={fieldName}
            formikProps={formikProps}
            invisibleFields={invisibleFields}
            updateInvisibleFields={updateInvisibleFields}
          />
        )
      })}
    </>
  )
}
CustomFields.propTypes = {
  fieldsConfig: PropTypes.object,
  formikProps: PropTypes.object,
  fieldNamePrefix: PropTypes.string.isRequired,
  invisibleFields: PropTypes.array,
  updateInvisibleFields: PropTypes.func
}
CustomFields.defaultProps = {
  fieldNamePrefix: DEFAULT_CUSTOM_FIELDS_PREFIX
}

const READONLY_FIELD_COMPONENTS = {
  [CUSTOM_FIELD_TYPE.TEXT]: ReadonlyTextField,
  [CUSTOM_FIELD_TYPE.NUMBER]: ReadonlyTextField,
  [CUSTOM_FIELD_TYPE.DATE]: ReadonlyDateField,
  [CUSTOM_FIELD_TYPE.DATETIME]: ReadonlyDateTimeField,
  [CUSTOM_FIELD_TYPE.ENUM]: ReadonlyEnumField,
  [CUSTOM_FIELD_TYPE.ENUMSET]: ReadonlyEnumField,
  [CUSTOM_FIELD_TYPE.ARRAY_OF_OBJECTS]: ReadonlyArrayOfObjectsField,
  [CUSTOM_FIELD_TYPE.SPECIAL_FIELD]: ReadonlySpecialField
}

export const ReadonlyCustomFields = ({
  fieldsConfig,
  formikProps,
  fieldNamePrefix
}) => {
  return (
    <>
      {Object.keys(fieldsConfig).map(key => {
        const fieldConfig = fieldsConfig[key]
        const {
          type,
          placeholder,
          helpText,
          validations,
          visibleWhen,
          objectFields,
          ...fieldProps
        } = fieldConfig
        let extraProps = {}
        if (type === CUSTOM_FIELD_TYPE.ARRAY_OF_OBJECTS) {
          extraProps = {
            fieldConfig,
            formikProps
          }
        }
        const FieldComponent = READONLY_FIELD_COMPONENTS[type]
        return (
          <FieldComponent
            key={key}
            name={`${fieldNamePrefix}.${key}`}
            {...fieldProps}
            {...extraProps}
          />
        )
      })}
    </>
  )
}
ReadonlyCustomFields.propTypes = {
  fieldsConfig: PropTypes.object,
  formikProps: PropTypes.object,
  fieldNamePrefix: PropTypes.string.isRequired
}
ReadonlyCustomFields.defaultProps = {
  fieldNamePrefix: DEFAULT_CUSTOM_FIELDS_PREFIX
}

// customFields should contain the JSON of all the visible custom fields
export const customFieldsJSONString = values => {
  const clonedValues = _cloneDeep(values)
  if (values.formCustomFields.invisibleCustomFields) {
    clonedValues.formCustomFields.invisibleCustomFields.forEach(f => {
      _set(clonedValues, f.split("."), undefined)
    })
  }
  return JSON.stringify(clonedValues.formCustomFields)
}
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { withRouter, Link } from 'react-router';
import { Grid, Row, Col, Button } from 'react-bootstrap';
import { FacebookShareButton, TwitterShareButton } from 'react-share';
import * as _ from 'lodash';
import withScriptjs from 'react-google-maps/lib/async/withScriptjs';
import { withGoogleMap, GoogleMap, Marker } from 'react-google-maps';
import fetchCampaignById from '../redux/actions/activeCampaign';
import fetchSignatures from '../redux/actions/signature';
import Discussion from '../components/Discussion';
import SignCampaign from '../components/SignCampaign';
import SignatureList from '../components/SignatureList';
import CollapsePanel from '.././components/CollapsePanel';
import { fetchApartmentsRequest } from '../redux/actions/initialSearch';
import CampaignProgressBar from '../components/CampaignProgressBar';
import CampaignStatus from '../components/CampaignStatus';

const CAMPAIGN_DURATION = 15;
class CampaignPage extends Component {
  componentDidMount() {
    this.props.fetchCampaignById(this.props.params.id);
    this.props.fetchSignatures(this.props.params.id);
    this.props.fetchApartmentsRequest();
  }

  componentWillUpdate(nextProps, nextState) {
    if (this.props.params.id !== nextProps.params.id) {
      nextProps.fetchCampaignById(nextProps.params.id);
      nextProps.fetchSignatures(nextProps.params.id);
    }
  }

  render() {
    const tools = [
      {
        title: 'Tips for Approaching your Landlord',
        to: '/tips-for-requesting'
      },
      {
        title: 'Denver Recycling Facts',
        to: '/denver-learn-more'
      }
    ];
    const toolsList = tools.map(tool => (

        <Link to={tool.to}>{tool.title}</Link>

    ));
    const {
      activeCampaign: { loading, loaded, campaign },
      initialSearch: { apartments }
    } = this.props;
    const hrefIsLocalhost = window.location.href.toLowerCase().includes('localhost');
    const MapWithAMarker = withRouter(
      withScriptjs(
        withGoogleMap(props => (
          <GoogleMap
            ref={props.onMapLoad}
            defaultZoom={10}
            defaultCenter={{ lat: 39.7392, lng: -104.9903 }}
            onClick={props.onMapClick}
          >
            {props.markers.map(marker => (
              <Marker
                key={marker.id}
                position={{ lat: marker.lat, lng: marker.lng }}
                onRightClick={() => props.onMarkerRightClick(marker)}
                onClick={() => props.router.push(`/campaign/${marker.id}`)}
                title={marker.street_address}
              />
            ))}
          </GoogleMap>
        ))
      )
    );

    const mapUrl = `https://maps.googleapis.com/maps/api/js?v=3.exp&key=${
      process.env.REACT_APP_GOOGLE_MAPS_KEY
    }`;

    return (
      <Grid className="">
        <Row>
          <Col md={9} xs={12} className="body">
            <Row className="show-grid">
              <Col md={7} xs={12} className="top center-block">

                  {campaign &&
                    campaign.address && (

{campaign.name}

                          {loading && }
                          {loaded && campaign && campaign.address}

                    )}

              </Col>
              <Col className="map center-block" md={3} xs={10}>

                  <MapWithAMarker
                    googleMapURL={mapUrl}
                    loadingElement={
}
                    containerElement={

                    }
                    mapElement={
}
                    onMapLoad={_.noop}
                    onMapClick={_.noop}
                    markers={apartments}
                    onMarkerRightClick={_.noop}
                  />

              </Col>
            </Row>
            <Row className="show-grid top">
              <Col className="social-bar" md={12} xs={12}>
                <Col md={3} xs={12} className="text-center share">

Share Your Campaign

                </Col>

                  <Col md={3} xs={6}>
                    <FacebookShareButton
                      quote="Support my recycling request!"
                      /*
                      facebook url errors on localhost, it has to be able to
                      connect to something.  so if its on dev link it to the heroku page.
                      */
                      url={
                        hrefIsLocalhost
                          ? `https://denver-reimagine.herokuapp.com/campaign/${
                            this.props.params.id
                          }`
                          : window.location.href
                      }
                    >
                      <Button bsStyle="remove-default" className="btn btn-facebook" block>
                        Facebook
                      </Button>
                    </FacebookShareButton>
                  </Col>
                  <Col md={3} xs={6}>
                    <TwitterShareButton
                      url={window.location.href}
                      title="Support my recycling request!"
                      via="EcoCycle"
                      hashtags={['ZeroWasteDenver', 'Recycle']}
                    >
                      <Button bsStyle="remove-default" className="btn btn-twitter" block>
                        Tweet
                      </Button>
                    </TwitterShareButton>
                  </Col>
                  <Col md={3} xs={12}>
                    <a
                      className="btn btn-flyer btn-block"
                      href={`${process.env.PUBLIC_URL}/flyer.pdf`}
                      target="_blank"
                    >
                       Download Flyer

                  </Col>

              </Col>
            </Row>
            {campaign &&
              campaign.createdAt && (
                <Row className="show-grid top">
                  <Col className="status-bar" md={8} xs={12}>
                    <Row>
                      <CampaignProgressBar
                        createdAt={campaign.createdAt}
                        phases={[
                          'Campaign Created',
                          'Print Flyers',
                          'Final Signatures',
                          'Request Recycling'
                        ]}
                        duration={CAMPAIGN_DURATION}
                      />
                    </Row>
                  </Col>
                  <Col className="status-bar" md={4} xs={12}>
                    <CampaignStatus createdAt={campaign.createdAt} duration={CAMPAIGN_DURATION} />
                  </Col>
                </Row>
              )}
            <Row className="show-grid top">
              <Col md={12} xs={12}>
                <Col md={3} xs={10} className="tools">

TOOLS:

{toolsList}

                </Col>
                <Col md={8} mdOffset={1} xs={10} className="tools">
                  <Discussion campaignID={this.props.params.id} />
                </Col>
              </Col>
            </Row>
          </Col>
          <Col md={3} xs={12} className="side-bar">
            <SignCampaign signatureObj={this.props.signature} />

              <CollapsePanel
                defaultExpanded
                titleText="See Who's Signed"
                body={<SignatureList signatures={this.props.signature.signatures} />}
              />

          </Col>
        </Row>
      </Grid>
    );
  }
}

CampaignPage.defaultProps = {
  markers: []
};

CampaignPage.propTypes = {
  initialSearch: PropTypes.shape({
    apartments: PropTypes.array.isRequired
  }).isRequired,
  activeCampaign: PropTypes.shape({
    campaign: PropTypes.shape({
      street_address: PropTypes.string
    }),
    loading: PropTypes.bool,
    loaded: PropTypes.bool
  }).isRequired,
  fetchCampaignById: PropTypes.func.isRequired,
  fetchSignatures: PropTypes.func.isRequired,
  fetchApartmentsRequest: PropTypes.func.isRequired,
  params: PropTypes.shape({
    id: PropTypes.string
  }).isRequired,
  signature: PropTypes.shape({
    loaded: PropTypes.bool.isRequired,
    loading: PropTypes.bool.isRequired,
    signatures: PropTypes.arrayOf(PropTypes.object),
    error: PropTypes.objectOf(PropTypes.any)
  }).isRequired
};

export default connect(
  ({ activeCampaign, signature, initialSearch }) => ({
    activeCampaign,
    signature,
    initialSearch
  }),
  {
    fetchCampaignById,
    fetchSignatures,
    fetchApartmentsRequest
  }
)(withRouter(CampaignPage));
<commit_msg>creation date first Monday after 21 days connect to #204 (#313)<commit_after>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { withRouter, Link } from 'react-router';
import { Grid, Row, Col, Button } from 'react-bootstrap';
import { FacebookShareButton, TwitterShareButton } from 'react-share';
import * as _ from 'lodash';
import withScriptjs from 'react-google-maps/lib/async/withScriptjs';
import { withGoogleMap, GoogleMap, Marker } from 'react-google-maps';
import fetchCampaignById from '../redux/actions/activeCampaign';
import fetchSignatures from '../redux/actions/signature';
import Discussion from '../components/Discussion';
import SignCampaign from '../components/SignCampaign';
import SignatureList from '../components/SignatureList';
import CollapsePanel from '.././components/CollapsePanel';
import { fetchApartmentsRequest } from '../redux/actions/initialSearch';
import CampaignProgressBar from '../components/CampaignProgressBar';
import CampaignStatus from '../components/CampaignStatus';

const MIN_CAMPAIGN_DURATION = 21;
const ONE_DAY_IN_MILLISECONDS = 1000 * 60 * 60 * 24;
class CampaignPage extends Component {
  componentDidMount() {
    this.props.fetchCampaignById(this.props.params.id);
    this.props.fetchSignatures(this.props.params.id);
    this.props.fetchApartmentsRequest();
  }

  componentWillUpdate(nextProps, nextState) {
    if (this.props.params.id !== nextProps.params.id) {
      nextProps.fetchCampaignById(nextProps.params.id);
      nextProps.fetchSignatures(nextProps.params.id);
    }
  }

  calculateCampaignDuration = creationDateString => {
    const creationDate = new Date(creationDateString);
    const expireDate = new Date(creationDateString);
    // the campaign is set to expire the first Monday 3-weeks after campaign creation
    expireDate.setDate(expireDate.getDate() + MIN_CAMPAIGN_DURATION);
    expireDate.setDate(expireDate.getDate() + (1 + 7 - expireDate.getDay()) % 7);

    const timeDiff = Math.abs(expireDate.getTime() - creationDate.getTime());
    const diffDays = Math.ceil(timeDiff / ONE_DAY_IN_MILLISECONDS);
    return diffDays;
  };

  render() {
    const tools = [
      {
        title: 'Tips for Approaching your Landlord',
        to: '/tips-for-requesting'
      },
      {
        title: 'Denver Recycling Facts',
        to: '/denver-learn-more'
      }
    ];
    const toolsList = tools.map(tool => (

        <Link to={tool.to}>{tool.title}</Link>

    ));
    const {
      activeCampaign: { loading, loaded, campaign },
      initialSearch: { apartments }
    } = this.props;
    const hrefIsLocalhost = window.location.href.toLowerCase().includes('localhost');
    const MapWithAMarker = withRouter(
      withScriptjs(
        withGoogleMap(props => (
          <GoogleMap
            ref={props.onMapLoad}
            defaultZoom={10}
            defaultCenter={{ lat: 39.7392, lng: -104.9903 }}
            onClick={props.onMapClick}
          >
            {props.markers.map(marker => (
              <Marker
                key={marker.id}
                position={{ lat: marker.lat, lng: marker.lng }}
                onRightClick={() => props.onMarkerRightClick(marker)}
                onClick={() => props.router.push(`/campaign/${marker.id}`)}
                title={marker.street_address}
              />
            ))}
          </GoogleMap>
        ))
      )
    );

    const mapUrl = `https://maps.googleapis.com/maps/api/js?v=3.exp&key=${
      process.env.REACT_APP_GOOGLE_MAPS_KEY
    }`;

    return (
      <Grid className="">
        <Row>
          <Col md={9} xs={12} className="body">
            <Row className="show-grid">
              <Col md={7} xs={12} className="top center-block">

                  {campaign &&
                    campaign.address && (

{campaign.name}

                          {loading && }
                          {loaded && campaign && campaign.address}

                    )}

              </Col>
              <Col className="map center-block" md={3} xs={10}>

                  <MapWithAMarker
                    googleMapURL={mapUrl}
                    loadingElement={
}
                    containerElement={

                    }
                    mapElement={
}
                    onMapLoad={_.noop}
                    onMapClick={_.noop}
                    markers={apartments}
                    onMarkerRightClick={_.noop}
                  />

              </Col>
            </Row>
            <Row className="show-grid top">
              <Col className="social-bar" md={12} xs={12}>
                <Col md={3} xs={12} className="text-center share">

Share Your Campaign

                </Col>

                  <Col md={3} xs={6}>
                    <FacebookShareButton
                      quote="Support my recycling request!"
                      /*
                      facebook url errors on localhost, it has to be able to
                      connect to something.  so if its on dev link it to the heroku page.
                      */
                      url={
                        hrefIsLocalhost
                          ? `https://denver-reimagine.herokuapp.com/campaign/${
                            this.props.params.id
                          }`
                          : window.location.href
                      }
                    >
                      <Button bsStyle="remove-default" className="btn btn-facebook" block>
                        Facebook
                      </Button>
                    </FacebookShareButton>
                  </Col>
                  <Col md={3} xs={6}>
                    <TwitterShareButton
                      url={window.location.href}
                      title="Support my recycling request!"
                      via="EcoCycle"
                      hashtags={['ZeroWasteDenver', 'Recycle']}
                    >
                      <Button bsStyle="remove-default" className="btn btn-twitter" block>
                        Tweet
                      </Button>
                    </TwitterShareButton>
                  </Col>
                  <Col md={3} xs={12}>
                    <a
                      className="btn btn-flyer btn-block"
                      href={`${process.env.PUBLIC_URL}/flyer.pdf`}
                      target="_blank"
                    >
                       Download Flyer

                  </Col>

              </Col>
            </Row>
            {campaign &&
              campaign.createdAt && (
                <Row className="show-grid top">
                  <Col className="status-bar" md={8} xs={12}>
                    <Row>
                      <CampaignProgressBar
                        createdAt={campaign.createdAt}
                        phases={[
                          'Campaign Created',
                          'Print Flyers',
                          'Final Signatures',
                          'Request Recycling'
                        ]}
                        duration={this.calculateCampaignDuration(campaign.createdAt)}
                      />
                    </Row>
                  </Col>
                  <Col className="status-bar" md={4} xs={12}>
                    <CampaignStatus
                      createdAt={campaign.createdAt}
                      duration={this.calculateCampaignDuration(campaign.createdAt)}
                    />
                  </Col>
                </Row>
              )}
            <Row className="show-grid top">
              <Col md={12} xs={12}>
                <Col md={3} xs={10} className="tools">

TOOLS:

{toolsList}

                </Col>
                <Col md={8} mdOffset={1} xs={10} className="tools">
                  <Discussion campaignID={this.props.params.id} />
                </Col>
              </Col>
            </Row>
          </Col>
          <Col md={3} xs={12} className="side-bar">
            <SignCampaign signatureObj={this.props.signature} />

              <CollapsePanel
                defaultExpanded
                titleText="See Who's Signed"
                body={<SignatureList signatures={this.props.signature.signatures} />}
              />

          </Col>
        </Row>
      </Grid>
    );
  }
}

CampaignPage.defaultProps = {
  markers: []
};

CampaignPage.propTypes = {
  initialSearch: PropTypes.shape({
    apartments: PropTypes.array.isRequired
  }).isRequired,
  activeCampaign: PropTypes.shape({
    campaign: PropTypes.shape({
      street_address: PropTypes.string
    }),
    loading: PropTypes.bool,
    loaded: PropTypes.bool
  }).isRequired,
  fetchCampaignById: PropTypes.func.isRequired,
  fetchSignatures: PropTypes.func.isRequired,
  fetchApartmentsRequest: PropTypes.func.isRequired,
  params: PropTypes.shape({
    id: PropTypes.string
  }).isRequired,
  signature: PropTypes.shape({
    loaded: PropTypes.bool.isRequired,
    loading: PropTypes.bool.isRequired,
    signatures: PropTypes.arrayOf(PropTypes.object),
    error: PropTypes.objectOf(PropTypes.any)
  }).isRequired
};

export default connect(
  ({ activeCampaign, signature, initialSearch }) => ({
    activeCampaign,
    signature,
    initialSearch
  }),
  {
    fetchCampaignById,
    fetchSignatures,
    fetchApartmentsRequest
  }
)(withRouter(CampaignPage));
<|endoftext|>


<commit_before>'use strict';

var structures = require('../lib/structures.js');

/*
  ======== A Handy Little Nodeunit Reference ========
  https://github.com/caolan/nodeunit

  Test methods:
    test.expect(numAssertions)
    test.done()
  Test assertions:
    test.ok(value, [message])
    test.equal(actual, expected, [message])
    test.notEqual(actual, expected, [message])
    test.deepEqual(actual, expected, [message])
    test.notDeepEqual(actual, expected, [message])
    test.strictEqual(actual, expected, [message])
    test.notStrictEqual(actual, expected, [message])
    test.throws(block, [error], [message])
    test.doesNotThrow(block, [error], [message])
    test.ifError(value)
*/

exports['awesome'] = {
  setUp: function(done) {
    // setup here
    done();
  },
  'no args': function(test) {
    test.expect(1);
    // tests here
    test.equal(structures.awesome(), 'awesome', 'should be awesome.');
    test.done();
  },
};
<commit_msg>Added placeholder test to test build system<commit_after>'use strict';

var structure = require('../lib/structure.js');

/*
  ======== A Handy Little Nodeunit Reference ========
  https://github.com/caolan/nodeunit

  Test methods:
    test.expect(numAssertions)
    test.done()
  Test assertions:
    test.ok(value, [message])
    test.equal(actual, expected, [message])
    test.notEqual(actual, expected, [message])
    test.deepEqual(actual, expected, [message])
    test.notDeepEqual(actual, expected, [message])
    test.strictEqual(actual, expected, [message])
    test.notStrictEqual(actual, expected, [message])
    test.throws(block, [error], [message])
    test.doesNotThrow(block, [error], [message])
    test.ifError(value)
*/

exports['awesome'] = {
  setUp: function(done) {
    // setup here
    done();
  },
  'no args': function(test) {
    test.expect(1);
    // tests here
    test.equal('test', 'test', 'should be placeholder test.');
    test.done();
  },
};
<|endoftext|>


<commit_before>import React from "react";
import { map, find } from "lodash-es";
import { connect } from "react-redux";
import { push } from "react-router-redux";
import { Link } from "react-router-dom";
import { LinkContainer } from "react-router-bootstrap";
import { Alert, Row, Col, ListGroup, Panel } from "react-bootstrap";

import { Flex, FlexItem, Icon, ListGroupItem, Pagination, ViewHeader, LoadingPlaceholder } from "../../base";
import OTUToolbar from "./Toolbar";
import CreateOTU from "./Create";
import { createFindURL } from "../../utils";

const OTUItem = ({ refId, abbreviation, id, name, modified, verified }) => (
    <LinkContainer to={`/refs/${refId}/otus/${id}`} key={id} className="spaced">
        <ListGroupItem bsStyle={verified ? null : "warning"}>
            <Row>
                <Col xs={11} md={7}>
                    {name}

                        {abbreviation}

                </Col>
                <Col xsHidden md={4}>
                    {abbreviation}
                </Col>
                <Col xs={1} md={1}>

                        {modified ? <Icon bsStyle="warning" name="flag" /> : null}

                </Col>
                {verified ? null : <Icon name="tag" pullRight tip="This OTU is unverified" />}
            </Row>
        </ListGroupItem>
    </LinkContainer>
);

const OTUsList = (props) => {

    let OTUComponents;

    if (props.documents === null) {
        return
;
    }

    const OTUCount = props.documents.length;

    if (OTUCount) {
        OTUComponents = map(props.documents, document =>
            <OTUItem key={document.id} refId={props.refId} {...document} />
        );
    } else {
        OTUComponents = (
            <ListGroupItem key="noOTUs" className="text-center">

                    <Icon name="info" /> No OTUs found,

                 <Link to={{state: {createOTU: true}}}>create</Link> some.
            </ListGroupItem>
        );
    }

    let alert;

    if (props.modified_count) {
        alert = (
            <Alert bsStyle="warning">
                <Flex alignItems="center">
                    <Icon name="info" />
                    <FlexItem pad={5}>
                        The OTU database has changed.
                        <Link to={`/refs/${props.refId}/indexes`}>Rebuild the index</Link>
                         to use the changes in further analyses.
                    </FlexItem>
                </Flex>
            </Alert>
        );
    }

    const importProgress = props.process ? find(props.processes, { id: props.process.id }).progress : 1;

    if (importProgress < 1) {
        return (
            <Panel>
                <Panel.Body>
                    <LoadingPlaceholder message="Import in progress" margin="1.2rem" />
                </Panel.Body>
            </Panel>
        );
    }

    return (

            <ViewHeader
                page={props.page}
                count={OTUCount}
                foundCount={props.found_count}
            />

            {alert}

            <OTUToolbar />

            <ListGroup>
                {OTUComponents}
            </ListGroup>

            <Pagination
                documentCount={OTUCount}
                onPage={props.onPage}
                page={props.page}
                pageCount={props.page_count}
            />

            <CreateOTU {...props} />

    );
};

const mapStateToProps = state => ({
    ...state.otus,
    account: state.account,
    refId: state.references.detail.id,
    process: state.references.detail.process,
    processes: state.processes.documents
});

const mapDispatchToProps = (dispatch) => ({

    onPage: (page) => {
        const url = createFindURL({ page });
        dispatch(push(url.pathname + url.search));
    },

    onHide: () => {
        dispatch(push({state: {createOTU: false}}));
    }
});

export default connect(mapStateToProps, mapDispatchToProps)(OTUsList);
<commit_msg>Fix rebuild index check and eslint<commit_after>import React from "react";
import { map, find } from "lodash-es";
import { connect } from "react-redux";
import { push } from "react-router-redux";
import { Link } from "react-router-dom";
import { LinkContainer } from "react-router-bootstrap";
import { Alert, Row, Col, ListGroup, Panel } from "react-bootstrap";

import { Flex, FlexItem, Icon, ListGroupItem, Pagination, ViewHeader, LoadingPlaceholder } from "../../base";
import OTUToolbar from "./Toolbar";
import CreateOTU from "./Create";
import { createFindURL } from "../../utils";

const OTUItem = ({ refId, abbreviation, id, name, modified, verified }) => (
    <LinkContainer to={`/refs/${refId}/otus/${id}`} key={id} className="spaced">
        <ListGroupItem bsStyle={verified ? null : "warning"}>
            <Row>
                <Col xs={11} md={7}>
                    {name}

                        {abbreviation}

                </Col>
                <Col xsHidden md={4}>
                    {abbreviation}
                </Col>
                <Col xs={1} md={1}>

                        {modified ? <Icon bsStyle="warning" name="flag" /> : null}

                </Col>
                {verified ? null : <Icon name="tag" pullRight tip="This OTU is unverified" />}
            </Row>
        </ListGroupItem>
    </LinkContainer>
);

const OTUsList = (props) => {

    let OTUComponents;

    if (props.documents === null) {
        return
;
    }

    const OTUCount = props.documents.length;

    if (OTUCount) {
        OTUComponents = map(props.documents, document =>
            <OTUItem key={document.id} refId={props.refId} {...document} />
        );
    } else {
        OTUComponents = (
            <ListGroupItem key="noOTUs" className="text-center">

                    <Icon name="info" /> No OTUs found,

                 <Link to={{state: {createOTU: true}}}>create</Link> some.
            </ListGroupItem>
        );
    }

    let alert;

    if (props.unbuiltChangeCount) {
        alert = (
            <Alert bsStyle="warning">
                <Flex alignItems="center">
                    <Icon name="info" />
                    <FlexItem pad={5}>
                        The OTU database has changed.
                        <Link to={`/refs/${props.refId}/indexes`}>Rebuild the index</Link>
                         to use the changes in further analyses.
                    </FlexItem>
                </Flex>
            </Alert>
        );
    }

    const importProgress = props.process ? find(props.processes, { id: props.process.id }).progress : 1;

    if (importProgress < 1) {
        return (
            <Panel>
                <Panel.Body>
                    <LoadingPlaceholder message="Import in progress" margin="1.2rem" />
                </Panel.Body>
            </Panel>
        );
    }

    return (

            <ViewHeader
                page={props.page}
                count={OTUCount}
                foundCount={props.found_count}
            />

            {alert}

            <OTUToolbar />

            <ListGroup>
                {OTUComponents}
            </ListGroup>

            <Pagination
                documentCount={OTUCount}
                onPage={props.onPage}
                page={props.page}
                pageCount={props.page_count}
            />

            <CreateOTU {...props} />

    );
};

const mapStateToProps = state => ({
    ...state.otus,
    account: state.account,
    refId: state.references.detail.id,
    process: state.references.detail.process,
    processes: state.processes.documents,
    unbuiltChangeCount: state.references.detail.unbuilt_change_count
});

const mapDispatchToProps = (dispatch) => ({

    onPage: (page) => {
        const url = createFindURL({ page });
        dispatch(push(url.pathname + url.search));
    },

    onHide: () => {
        dispatch(push({state: {createOTU: false}}));
    }
});

export default connect(mapStateToProps, mapDispatchToProps)(OTUsList);
<|endoftext|>


<commit_before>
/** section: scripty2 ui
 * S2.UI.Mixin
**/
S2.UI.Mixin = {};

//= require "mixin/configurable"
//= require "mixin/trackable"
//= require "mixin/element"

//= require "mixin/shim"<commit_msg>a few words on mixins<commit_after>/** section: scripty2 ui
 * S2.UI.Mixin
 *
 * Reusable mixins for user interface elements.
 *
 * * [[S2.UI.Mixin.Configurable]] for hassle-free blending of
 *   default options with user-defined options.
 * * [[S2.UI.Mixin.Trackable]]
 * * [[S2.UI.Mixin.Element]] provides convenience methods for widgets
 *   that map easily to a single element.
 * * [[S2.UI.Mixin.Shim]] provides an implementation of a "shim" for Internet Explorer 6,
 *   to avoid rendering z-order problems on that browser.
**/
S2.UI.Mixin = {};

//= require "mixin/configurable"
//= require "mixin/trackable"
//= require "mixin/element"

//= require "mixin/shim"<|endoftext|>