<commit_before>var parseColor = function() {

    var colors = [
      "#FFFFFF",
      "#000000",
      "#000080",
      "#008000",

      "#ff0000",
      "#800040",
      "#800080",
      "#ff8040",

      "#ffff00",
      "#80ff00",
      "#008000",
      "#00ffff",

      "#0000ff",
      "#FF55FF",
      "#808080",
      "#C0C0C0"
    ];

    var style = {
      2 : {
        styles : [
          {
            key : "font-weight",
            value : "bold"
          }
        ]
      },
      15: {
        styles : [
          {
            key : "background",
            value : null
          },
          {
            key : "color",
            value : null
          },
          {
            key : "font-style",
            value : null
          },
          {
            key : "text-decoration",
            value : null
          },
          {
            key : "font-weight",
            value : null
          }
        ]
      },
      22: {
        styles:[]
      },
      29: {
        styles : [
          {
            key : "font-style",
            value : "italic"
          }
        ]
      },
      31: {
        styles : [
          {
            key : "text-decoration",
            value : "underline"
          }
        ]
      },
      3 : function(str){
        var temp, color, background,style
        style = [];
        temp = str.substring(1).split(",")
        color = colors[parseInt(temp[0])];
        if (color) {
          style.push({
            key : "color",
            value : color
          })
        }
        if (temp[1]) {
          background = colors[parseInt(temp[1])];
          if (background) {
            style.push({
              key : "background",
              value : background
            })
          }
        }

        return {
          styles : style
        };

      }
    }

    function copyOver(from, to) {
      for(var i in from) {
        if(from.hasOwnProperty(i)) {
          to[i] = from[i];
        }
      }
    }

    function wrap(text, styles) {
      if (text == null) {
        return "";
      }
      var i
      var text2 = "";
      text2 += text;
      text2 += "";
      return text2;
    }

    var parseColor_ = function(html) {

        var html, matchrule, matchrule2, allStyle, temp, text, styles, stylefrag, i;
        allStyle = [2, 15, 22, 29, 31, 3];
        matchrule = /((?:\u0003\d\d?,\d\d?|\u0003\d\d?|\u0002|\u001d|\u000f|\u0016|\u001f)+)/g;
        /* use "html" to prevent break links*/
        //html = $(this).html();
        temp = html.split(matchrule);
        if (temp.length === 1) {
          /*no color code, so break it at early at possible*/
          return html;
        }
        for (i = 0; i < temp.length; i++) {
          text = temp[i];
          if (allStyle.indexOf(text.charCodeAt(0)) < 0) {
            continue;
          }
          styles = {};
          matchrule2 = /((?:\u0003\d\d?,\d\d?|\u0003\d\d?|\u0002|\u001d|\u000f|\u0016|\u001f))/g;
          fragTemp = matchrule2.exec(text);

          if (!fragTemp) {
            continue;
          }

          stylefrag = [];
          stylefrag.push(fragTemp[1]);
          while (fragTemp = matchrule2.exec(text)) {
            stylefrag.push(fragTemp[1]);
          }

          // extract styles from style frag
          stylefrag.forEach(function(el){
            var temp2, i
            var charCode = el.charCodeAt(0);
            if (style[charCode]) {
              temp2 = style[charCode]
              if (typeof temp2 === "function") {
                temp2 = temp2(el);
              }
              for (i = 0; i< temp2.styles.length; i++) {
                styles[temp2.styles[i].key] = temp2.styles[i].value;
              }
            }
            return true;
          });
          //console.log(JSON.stringify(text));
          //console.log(JSON.stringify(stylefrag));
          //insert styles into list and remove parsed tag
          temp.splice(i, 1, styles);
        }

        var styleTemp = {};
        for (i = 0; i < temp.length; i++) {
          if (typeof temp[i] === "object") {
            copyOver(temp[i], styleTemp);
            copyOver(styleTemp, temp[i]);
          }
        }

        for (i = 0; i < temp.length; i++) {
          if (typeof temp[i] === "object") {
            temp.splice(i, 2, wrap(temp[i + 1], temp[i]));
          }
        }

        return temp.join('')

    };

    return parseColor_;
} ();

var getColor = function() {
  var cache = {};
  function getColor_(str) {
    if (cache[str]) {
      return cache[str];
    }

    if (typeof md5 !== "undefined") {
      var prefix = str.charAt(0);
      var name = ((prefix == 'ⓢ' || prefix == '🅣') ? str.substr(2) : str);
      var frag = parseInt(md5(name).substring(0,6), 16);
    } else {
      var frag = Math.floor(Math.random() * 0xffffff);
      console.log('missing md5 support, using random color now!')
    }

    var h = Math.floor((frag & 0xff0000 >> 16) / 255 * 360);
    var s = Math.floor((frag & 0xff00 >> 8) / 255 * 60 + 20);
    var l = Math.floor((frag & 0xff) / 255 * 20 + 50);

    //convert color with jQuery
    var colorCode = $('').css("color", "hsl(" + h + "," + s + "%," + l +"%)").css("color");

    cache[str] = colorCode;
    return colorCode;
  }
  return getColor_;
}();

$(document).ready(function(){
  $("ul.logs li").each(function(){
    var nickField = $(this).children(".nick");

    nickField.css("color",getColor(nickField.text()));
  });
  $(".wordwrap").each(function(){
    $(this).html(
      emoji(parseColor($(this).html()))
    );
  });
});

var strftime = function(date) {
  var hour = date.getHours(),
      min  = date.getMinutes(),
      sec  = date.getSeconds();

  if (hour < 10) { hour = "0" + hour; }
  if ( min < 10) {  min = "0" +  min; }
  if ( sec < 10) {  sec = "0" +  sec; }

  return hour + ":" + min + ":" + sec;
};

var link = function(klass, href, title) {
  return $('');
};

var lastTimestamp = undefined;
var seenTimestamp = {};
var pollNewMsg = function(isWidget) {
  var isWidget = (isWidget == null) ? false : isWidget;
  var time = lastTimestamp || (new Date()).getTime() / 1000.0;
  $.ajax({
    url: "/comet/poll/" + channel + "/" + time + "/updates.json",
    type: "get",
    async: true,
    cache: false,
    timeout: 60000,

    success: function (data) {
      var msgs = JSON.parse(data || '[]');
      for (var i = 0; i < msgs.length; i++) {
        var msg = msgs[i];
        if (seenTimestamp[msg.time]) { continue; }
        seenTimestamp[msg.time] = true;
        var date = new Date(parseFloat(msg["time"]) * 1000);
        var lis  = $(".logs > li").length;
        var url  = $("#today").text();

        // $("#today").text() gets nothing automatically when isWidget
        var msgElement = $("
").addClass("new-arrival")
          .append(link('time', url + '#' + lis, '#' + lis)
                    .text(strftime(date)))
          .append(link('nick', url + '/' + lis, msg['nick'])
                    .text(msg['nick'])
                    .css("color",getColor(msg['nick'])))
          .append($("").html(emoji(msg["msg"])).each(function(){
            $(this).html(
              parseColor($(this).html())
            );
          }));
        if (isWidget) {
          $(".logs").prepend(msgElement);
        }
        else {
          $(".logs").append(msgElement);
        }
      }

      // there's new message
      if (msgs.length > 0) {
        if (isWidget) {
          // widget layout
          $(document).scrollTop(0);
        }
        else {
          // desktop or mobile layout, there's a switch to turn off auto-scrolling
          if (window.can_scroll) {
            $(document).scrollTop($(document).height());
          }
        }

        // if we're in desktop version
        if (typeof Cocoa !== "undefined" && Cocoa !== null) {
          Cocoa.requestUserAttention();
          Cocoa.addUnreadCountToBadgeLabel(msgs.length);
        }
      }
lastTimestamp = (new Date()).getTime() / 1000.0;
try {
      lastTimestamp = msgs[msgs.length - 1]["time"];
} catch (e) {};

setTimeout(function(){
      pollNewMsg(isWidget);
}, 3000);
    },

    error: function() {
setTimeout(function(){
      pollNewMsg(isWidget);
}, Math.round(Math.random() * 3000 + 3000));
    }
  });
}

var enableDatePicker = function() {
  var useJqueryUIDatePicker = !Modernizr.inputtypes.date;

  $('#date-picker').on('change', function(event) {
    var targetDate = this.value;
    if (targetDate !== 'other') {
      location.href = location.href.replace(/[^\/]+$/, targetDate);
    } else {
      $( "#other-date-dialog" ).dialog( "open" );
    }
  });
  $( "#other-date-dialog" ).dialog({
    autoOpen: false,
    resizable: false,
    height:280,
    width:360,
    modal: true,
    open: function (event, ui) {
      if (useJqueryUIDatePicker) {
        if ($( ".ui-datepicker" ).is( ":visible" )) {
          $( "#other-date-picker" ).datepicker( "disable" );
        }
        $( "#other-date-picker" ).datepicker( "enable" );
      }
      $( "#other-date-picker" ).val( currentDay );
    },
    buttons: {
      "cancel": function() {
        $( this ).dialog( "close" );
      },
      "go": function() {
        $( this ).dialog( "close" );
        var targetDate = $( "#other-date-picker" ).val();
        location.href = location.href.replace(/[^\/]+$/, targetDate);
      }
    },
    beforeClose: function() {
      $( "#other-date-picker" ).datepicker("disable");
      if ($("#today").text() === currentDay) {
        $( "#date-picker" ).val( "today" );
      } else {
        $( "#date-picker" ).val( currentDay );
      }
    }
  });
  $( "#other-date-picker" ).on ( "keydown", function(e) {
    $( "#other-date-picker" ).datepicker( "hide" );
    if(e.which == 13) {
      $( "#other-date-dialog" ).dialog( "close" );
      $( "#date-picker" ).val( currentDay );
      var targetDate = $("#other-date-picker").val();
      location.href = location.href.replace(/[^\/]+$/, targetDate);
    }
  });

  var enableJqueryUIDatePicker = function() {
    var $input = $( "#other-date-picker" );
    /**
    IE sucks
    http://stackoverflow.com/questions/13010463/avoid-reopen-datepicker-after-select-a-date
    */
    $( "#other-date-picker" ).datepicker({
      fixFocusIE: false,
      onSelect: function(dateText, inst) {
        this.fixFocusIE = true
        $(this).change().focus();
      },
      onClose: function(dateText, inst) {
        this.fixFocusIE = true;
        this.focus();
      },
      beforeShow: function(input, inst) {
        var result = $.browser.msie ? !this.fixFocusIE : true;
        this.fixFocusIE = false;
        return result;
      }
    });
    $( "#other-date-picker" ).datepicker( "option", "dateFormat", "yy-mm-dd");
    $( "#other-date-picker" ).datepicker( "option", "maxDate", today );
    $( "#other-date-picker" ).datepicker( "option", "showAnim", "" );
    $( "#other-date-picker" ).datepicker("disable");
  }

  if (useJqueryUIDatePicker) {
    enableJqueryUIDatePicker();
  }
}

enableDatePicker();

$(".scroll_switch").click(function() {
  window.can_scroll = $(".scroll_switch").hasClass("scroll_switch_off");
  $(".scroll_switch").toggleClass("scroll_switch_off");
});

var pageScrollTop = function(position) {
  $("html, body").animate({
    scrollTop: position
  }, 1000);
};
<commit_msg>Make channel pages emit their date and message index<commit_after>var parseColor = function() {

    var colors = [
      "#FFFFFF",
      "#000000",
      "#000080",
      "#008000",

      "#ff0000",
      "#800040",
      "#800080",
      "#ff8040",

      "#ffff00",
      "#80ff00",
      "#008000",
      "#00ffff",

      "#0000ff",
      "#FF55FF",
      "#808080",
      "#C0C0C0"
    ];

    var style = {
      2 : {
        styles : [
          {
            key : "font-weight",
            value : "bold"
          }
        ]
      },
      15: {
        styles : [
          {
            key : "background",
            value : null
          },
          {
            key : "color",
            value : null
          },
          {
            key : "font-style",
            value : null
          },
          {
            key : "text-decoration",
            value : null
          },
          {
            key : "font-weight",
            value : null
          }
        ]
      },
      22: {
        styles:[]
      },
      29: {
        styles : [
          {
            key : "font-style",
            value : "italic"
          }
        ]
      },
      31: {
        styles : [
          {
            key : "text-decoration",
            value : "underline"
          }
        ]
      },
      3 : function(str){
        var temp, color, background,style
        style = [];
        temp = str.substring(1).split(",")
        color = colors[parseInt(temp[0])];
        if (color) {
          style.push({
            key : "color",
            value : color
          })
        }
        if (temp[1]) {
          background = colors[parseInt(temp[1])];
          if (background) {
            style.push({
              key : "background",
              value : background
            })
          }
        }

        return {
          styles : style
        };

      }
    }

    function copyOver(from, to) {
      for(var i in from) {
        if(from.hasOwnProperty(i)) {
          to[i] = from[i];
        }
      }
    }

    function wrap(text, styles) {
      if (text == null) {
        return "";
      }
      var i
      var text2 = "";
      text2 += text;
      text2 += "";
      return text2;
    }

    var parseColor_ = function(html) {

        var html, matchrule, matchrule2, allStyle, temp, text, styles, stylefrag, i;
        allStyle = [2, 15, 22, 29, 31, 3];
        matchrule = /((?:\u0003\d\d?,\d\d?|\u0003\d\d?|\u0002|\u001d|\u000f|\u0016|\u001f)+)/g;
        /* use "html" to prevent break links*/
        //html = $(this).html();
        temp = html.split(matchrule);
        if (temp.length === 1) {
          /*no color code, so break it at early at possible*/
          return html;
        }
        for (i = 0; i < temp.length; i++) {
          text = temp[i];
          if (allStyle.indexOf(text.charCodeAt(0)) < 0) {
            continue;
          }
          styles = {};
          matchrule2 = /((?:\u0003\d\d?,\d\d?|\u0003\d\d?|\u0002|\u001d|\u000f|\u0016|\u001f))/g;
          fragTemp = matchrule2.exec(text);

          if (!fragTemp) {
            continue;
          }

          stylefrag = [];
          stylefrag.push(fragTemp[1]);
          while (fragTemp = matchrule2.exec(text)) {
            stylefrag.push(fragTemp[1]);
          }

          // extract styles from style frag
          stylefrag.forEach(function(el){
            var temp2, i
            var charCode = el.charCodeAt(0);
            if (style[charCode]) {
              temp2 = style[charCode]
              if (typeof temp2 === "function") {
                temp2 = temp2(el);
              }
              for (i = 0; i< temp2.styles.length; i++) {
                styles[temp2.styles[i].key] = temp2.styles[i].value;
              }
            }
            return true;
          });
          //console.log(JSON.stringify(text));
          //console.log(JSON.stringify(stylefrag));
          //insert styles into list and remove parsed tag
          temp.splice(i, 1, styles);
        }

        var styleTemp = {};
        for (i = 0; i < temp.length; i++) {
          if (typeof temp[i] === "object") {
            copyOver(temp[i], styleTemp);
            copyOver(styleTemp, temp[i]);
          }
        }

        for (i = 0; i < temp.length; i++) {
          if (typeof temp[i] === "object") {
            temp.splice(i, 2, wrap(temp[i + 1], temp[i]));
          }
        }

        return temp.join('')

    };

    return parseColor_;
} ();

var getColor = function() {
  var cache = {};
  function getColor_(str) {
    if (cache[str]) {
      return cache[str];
    }

    if (typeof md5 !== "undefined") {
      var prefix = str.charAt(0);
      var name = ((prefix == 'ⓢ' || prefix == '🅣') ? str.substr(2) : str);
      var frag = parseInt(md5(name).substring(0,6), 16);
    } else {
      var frag = Math.floor(Math.random() * 0xffffff);
      console.log('missing md5 support, using random color now!')
    }

    var h = Math.floor((frag & 0xff0000 >> 16) / 255 * 360);
    var s = Math.floor((frag & 0xff00 >> 8) / 255 * 60 + 20);
    var l = Math.floor((frag & 0xff) / 255 * 20 + 50);

    //convert color with jQuery
    var colorCode = $('').css("color", "hsl(" + h + "," + s + "%," + l +"%)").css("color");

    cache[str] = colorCode;
    return colorCode;
  }
  return getColor_;
}();

$(document).ready(function(){
  $("ul.logs li").each(function(){
    var nickField = $(this).children(".nick");

    nickField.css("color",getColor(nickField.text()));
  });
  $(".wordwrap").each(function(){
    $(this).html(
      emoji(parseColor($(this).html()))
    );
  });
});

var strftime = function(date) {
  var hour = date.getHours(),
      min  = date.getMinutes(),
      sec  = date.getSeconds();

  if (hour < 10) { hour = "0" + hour; }
  if ( min < 10) {  min = "0" +  min; }
  if ( sec < 10) {  sec = "0" +  sec; }

  return hour + ":" + min + ":" + sec;
};

var link = function(klass, href, title) {
  return $('');
};

var lastTimestamp = undefined;
var seenTimestamp = {};
var pollNewMsg = function(isWidget) {
  var isWidget = (isWidget == null) ? false : isWidget;
  var time = lastTimestamp || (new Date()).getTime() / 1000.0;
  $.ajax({
    url: "/comet/poll/" + channel + "/" + time + "/updates.json",
    type: "get",
    async: true,
    cache: false,
    timeout: 60000,

    success: function (data) {
      var msgs = JSON.parse(data || '[]');
      for (var i = 0; i < msgs.length; i++) {
        var msg = msgs[i];
        if (seenTimestamp[msg.time]) { continue; }
        seenTimestamp[msg.time] = true;
        var date = new Date(parseFloat(msg["time"]) * 1000);
        var lis  = $(".logs > li").length;
        var url  = $("#today").text();

        // $("#today").text() gets nothing automatically when isWidget
        var msgElement = $("
").addClass("new-arrival")
          .append(link('time', url + '#' + lis, '#' + lis)
                    .text(strftime(date)))
          .append(link('nick', url + '/' + lis, msg['nick'])
                    .text(msg['nick'])
                    .css("color",getColor(msg['nick'])))
          .append($("").html(emoji(msg["msg"])).each(function(){
            $(this).html(
              parseColor($(this).html())
            );
          }));
        if (isWidget) {
          $(".logs").prepend(msgElement);
        }
        else {
          $(".logs").append(msgElement);
        }
      }

      // there's new message
      if (msgs.length > 0) {
        if (isWidget) {
          // widget layout
          $(document).scrollTop(0);
        }
        else {
          // desktop or mobile layout, there's a switch to turn off auto-scrolling
          if (window.can_scroll) {
            $(document).scrollTop($(document).height());
          }
        }

        // if we're in desktop version
        if (typeof Cocoa !== "undefined" && Cocoa !== null) {
          Cocoa.requestUserAttention();
          Cocoa.addUnreadCountToBadgeLabel(msgs.length);
        }
      }
lastTimestamp = (new Date()).getTime() / 1000.0;
try {
      lastTimestamp = msgs[msgs.length - 1]["time"];
} catch (e) {};

setTimeout(function(){
      pollNewMsg(isWidget);
}, 3000);
    },

    error: function() {
setTimeout(function(){
      pollNewMsg(isWidget);
}, Math.round(Math.random() * 3000 + 3000));
    }
  });
}

// post selected message index
$('.logs').on('click', '.time', function() {
  var friend = window.parent || window.opener;
  var index;
  if (friend) {
    index = +$(this).attr('title').substr(1)
    friend.postMessage({ type: 'LOGBOT_MESSAGE', index: index }, '*')
  }
})

// post current date
function postDateUpdate(date) {
  var friend = window.parent || window.opener;
  if (friend) {
    friend.postMessage({ type: 'LOGBOT_DATE', date: date }, '*')
  }
}
var enableDatePicker = function() {
  var useJqueryUIDatePicker = !Modernizr.inputtypes.date;

  $('#date-picker').on('change', function(event) {
    var targetDate = this.value;
    if (targetDate !== 'other') {
      location.href = location.href.replace(/[^\/]+$/, targetDate);
      postDateUpdate(targetDate)
    } else {
      $( "#other-date-dialog" ).dialog( "open" );
    }
  });
  $( "#other-date-dialog" ).dialog({
    autoOpen: false,
    resizable: false,
    height:280,
    width:360,
    modal: true,
    open: function (event, ui) {
      if (useJqueryUIDatePicker) {
        if ($( ".ui-datepicker" ).is( ":visible" )) {
          $( "#other-date-picker" ).datepicker( "disable" );
        }
        $( "#other-date-picker" ).datepicker( "enable" );
      }
      $( "#other-date-picker" ).val( currentDay );
    },
    buttons: {
      "cancel": function() {
        $( this ).dialog( "close" );
      },
      "go": function() {
        $( this ).dialog( "close" );
        var targetDate = $( "#other-date-picker" ).val();
        location.href = location.href.replace(/[^\/]+$/, targetDate);
        postDateUpdate(targetDate)
      }
    },
    beforeClose: function() {
      $( "#other-date-picker" ).datepicker("disable");
      if ($("#today").text() === currentDay) {
        $( "#date-picker" ).val( "today" );
      } else {
        $( "#date-picker" ).val( currentDay );
      }
    }
  });
  $( "#other-date-picker" ).on ( "keydown", function(e) {
    $( "#other-date-picker" ).datepicker( "hide" );
    if(e.which == 13) {
      $( "#other-date-dialog" ).dialog( "close" );
      $( "#date-picker" ).val( currentDay );
      var targetDate = $("#other-date-picker").val();
      location.href = location.href.replace(/[^\/]+$/, targetDate);
      postDateUpdate(targetDate)
    }
  });

  var enableJqueryUIDatePicker = function() {
    var $input = $( "#other-date-picker" );
    /**
    IE sucks
    http://stackoverflow.com/questions/13010463/avoid-reopen-datepicker-after-select-a-date
    */
    $( "#other-date-picker" ).datepicker({
      fixFocusIE: false,
      onSelect: function(dateText, inst) {
        this.fixFocusIE = true
        $(this).change().focus();
      },
      onClose: function(dateText, inst) {
        this.fixFocusIE = true;
        this.focus();
      },
      beforeShow: function(input, inst) {
        var result = $.browser.msie ? !this.fixFocusIE : true;
        this.fixFocusIE = false;
        return result;
      }
    });
    $( "#other-date-picker" ).datepicker( "option", "dateFormat", "yy-mm-dd");
    $( "#other-date-picker" ).datepicker( "option", "maxDate", today );
    $( "#other-date-picker" ).datepicker( "option", "showAnim", "" );
    $( "#other-date-picker" ).datepicker("disable");
  }

  if (useJqueryUIDatePicker) {
    enableJqueryUIDatePicker();
  }
}

enableDatePicker();

$(".scroll_switch").click(function() {
  window.can_scroll = $(".scroll_switch").hasClass("scroll_switch_off");
  $(".scroll_switch").toggleClass("scroll_switch_off");
});

var pageScrollTop = function(position) {
  $("html, body").animate({
    scrollTop: position
  }, 1000);
};
<|endoftext|>


<commit_before>'use strict';

var fs = require('fs'),
    moment = require('moment'),
    _ = require('lodash'),
    request = require('request').defaults({ encoding: null }),
    User = require('./user');

module.exports = function(config, bot, listener) {

    var logger = config.logger;

    var pokemon = JSON.parse(fs.readFileSync('./locale/pokemon.en.json'));
    var seen = []; // Contains encounter ids of already processed pokemen

    logger.level = 'debug';

    // Webhook receives pokemon spawn info.
    // Unfiltered at this point
    listener.on('pokemon', function(payload) {

        if (seen.indexOf(payload.encounter_id) !== -1) {
            return;
        }

        logger.info(
            'A wild %s appeared!\t Disappear time %s',
            pokemon[payload.pokemon_id],
            payload.disappear_time
        );

        seen.push(payload.encounter_id);
        logger.debug('Seen pokemon:\t %s', seen.length);

        // Find all users that are active and watching this pokemon
        User.find({ active: true, watchlist: Number(payload.pokemon_id) })
            .then(function(users) {
                logger.debug('Users watching this Pokemon: %s', _.map(users, 'telegramId'));

                var userIds = users.map(function(user) {
                    return user.telegramId;
                });
                sendPhoto(userIds, payload);
            });

    });

    setInterval(function() {
        seen = [];
        logger.debug('Cleared seen pokemon');
    }, 15 * 60 * 1000);

    function sendPhoto(users, payload) {
        var photo = getMap(payload.latitude, payload.longitude, function(err, res, body) {

            if (res.statusCode !== 200) {
                logger.error('Request failed with code %s', res.statusCode);
                logger.error('Make sure you have Static Maps API enabled on your key.');
                throw new Error('Failed to get map image from Google Maps API.');
            }

            bot.sendPhotoNotification(
                users,
                body,
                'A wild ' + pokemon[payload.pokemon_id] + ' appeared!\n' +
                'Disappears at ' + disappearTime(payload.disappear_time) + '\n' +
                '(' + timeToDisappear(payload.disappear_time) + ' left)'
            );
        });
    }

    function timeToDisappear(timestamp) {
        var diff = moment.unix(timestamp).diff(moment());
        return moment.duration(diff).humanize();
    }

    function disappearTime(timestamp) {
        var time = moment.unix(timestamp);
        return [time.hour(), time.minutes(), time.seconds()].join(':');
    }

    function getMap(lat, lon, cb) {
        return request({
            method: 'GET',
               uri: 'https://maps.googleapis.com/maps/api/staticmap',
               qs: {
                   center: lat + ',' + lon,
                   zoom: 15,
                   size: '640x640',
                   key: config.gmap_key,
                   markers: lat + ',' + lon
               },
               callback: cb
        });
    };

};
<commit_msg>Improved encounter logging<commit_after>'use strict';

var fs = require('fs'),
    moment = require('moment'),
    _ = require('lodash'),
    request = require('request').defaults({ encoding: null }),
    User = require('./user');

module.exports = function(config, bot, listener) {

    var logger = config.logger;

    var pokemon = JSON.parse(fs.readFileSync('./locale/pokemon.en.json'));
    var seen = []; // Contains encounter ids of already processed pokemen

    logger.level = 'debug';

    // Webhook receives pokemon spawn info.
    // Unfiltered at this point
    listener.on('pokemon', function(payload) {

        if (seen.indexOf(payload.encounter_id) !== -1) {
            return;
        }

        seen.push(payload.encounter_id);

        // Find all users that are active and watching this pokemon
        User.find({ active: true, watchlist: Number(payload.pokemon_id) })
            .then(function(users) {
                logger.info(
                    'Wild %s appeared!\t Disappear time %s\t Users watching %s\t Seen pokemon %s',
                    pokemon[payload.pokemon_id],
                    payload.disappear_time,
                    _.map(users, 'telegramId'),
                    seen.length
                );

                var userIds = users.map(function(user) {
                    return user.telegramId;
                });
                sendPhoto(userIds, payload);
            });

    });

    setInterval(function() {
        seen = [];
        logger.debug('Cleared seen pokemon');
    }, 15 * 60 * 1000);

    function sendPhoto(users, payload) {
        var photo = getMap(payload.latitude, payload.longitude, function(err, res, body) {

            if (res.statusCode !== 200) {
                logger.error('Request failed with code %s', res.statusCode);
                logger.error('Make sure you have Static Maps API enabled on your key.');
                throw new Error('Failed to get map image from Google Maps API.');
            }

            bot.sendPhotoNotification(
                users,
                body,
                'A wild ' + pokemon[payload.pokemon_id] + ' appeared!\n' +
                'Disappears at ' + disappearTime(payload.disappear_time) + '\n' +
                '(' + timeToDisappear(payload.disappear_time) + ' left)'
            );
        });
    }

    function timeToDisappear(timestamp) {
        var diff = moment.unix(timestamp).diff(moment());
        return moment.duration(diff).humanize();
    }

    function disappearTime(timestamp) {
        var time = moment.unix(timestamp);
        return [time.hour(), time.minutes(), time.seconds()].join(':');
    }

    function getMap(lat, lon, cb) {
        return request({
            method: 'GET',
               uri: 'https://maps.googleapis.com/maps/api/staticmap',
               qs: {
                   center: lat + ',' + lon,
                   zoom: 15,
                   size: '640x640',
                   key: config.gmap_key,
                   markers: lat + ',' + lon
               },
               callback: cb
        });
    };

};
<|endoftext|>


<commit_before>// This module defines a reusable Chord Diagram using D3.
define(function(require) {

  // Load D3 v4 via our custom bundle build output.
  var d3 = require("../d3/d3.min");

  // Constructor function for a Chord Diagram.
  // Accepts a container `div` DOM element.
  return function ChordDiagram(div){

    // Configuration parameters.
    var width = 500,
        height = 500,
        outerPadding = 50,
        arcThickness = 20,
        padAngle = 0.07,
        outerRadius = width / 2 - outerPadding,
        innerRadius = outerRadius - arcThickness,
        labelPadding = 10,
        selectedRibbon = null,
        onSelectedRibbonChangeCallback = function (){};

    // These "column" variables represent keys in the row objects of the input table.
    // Kibana's `tabify` happens to give us column names "1", "2", and "3".
    // These correspond to the Schemas defined for the plugin.
    var chordWeightColumn = "1",
        chordSourceColumn = "2",
        chordDestinationColumn = "3";

    // Accessor functions for columns.
    var weight = function (d){ return d[chordWeightColumn]; },
        source = function (d){ return d[chordSourceColumn]; },
        destination = function (d){ return d[chordDestinationColumn]; };

    // D3 Local objects for DOM-local storage of label angles and
    // whether or not labels should be flipped upside-down.
    var angle = d3.local(),
        flip = d3.local();

    // DOM Elements.
    var svg = d3.select(div).append("svg")
          .attr("width", width)
          .attr("height", height),
        g = svg.append("g")
          .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"),
        ribbonsG = g.append("g"),
        chordGroupsG = g.append("g");

    // D3 layouts, shapes and scales.
    var ribbon = d3.ribbon()
          .radius(innerRadius),
        chord = d3.chord()
          .padAngle(padAngle),
        color = d3.scaleOrdinal()
          .range(d3.schemeCategory20),
        arc = d3.arc()
          .innerRadius(innerRadius)
          .outerRadius(outerRadius);

    // Renders the given data as a chord diagram.
    function my(data){

      // Pre-process the data and calculate the Chord Diagram layout.
      var matrix = generateMatrix(data),
          chords = chord(matrix);

      // Render the ribbons of the Chord Diagram (the connecting fibers inside the circle).
      var ribbons = ribbonsG
        .selectAll("path")
          .data(chords);
      ribbons.enter().append("path").merge(ribbons)
        .attr("d", ribbon)
        .style("fill", function(d) {
          return color(d.source.index);
        })
        .style("opacity", function (d){

          // If there is a currently selected ribbon,
          if(selectedRibbon){

            // show the selected chord in full color,
            if(
              (selectedRibbon.sourceIndex === d.source.index)
              &&
              (selectedRibbon.targetIndex === d.target.index)
            ){
              return 1;
            } else {

              // and show all others faded out.
              return 0.1;
            }
          } else {

            // If there is no currently selected ribbon,
            // then show all ribbons with slight transparency.
            return 0.6;
          }
        })
        .style("stroke", "black")
        .style("stroke-opacity", 0.2)
        .on("mousedown", function (d){
          selectedRibbon = {
            sourceIndex: d.source.index,
            targetIndex: d.target.index,
            source: matrix.names[d.source.index],
            destination: matrix.names[d.target.index]
          };
          my(data);
          onSelectedRibbonChangeCallback();
        });
      ribbons.exit().remove();

      // Scaffold the chord groups.
      var chordGroups = chordGroupsG.selectAll("g").data(chords.groups);
      var chordGroupsEnter = chordGroups.enter().append("g");
      chordGroupsEnter.append("text");
      chordGroupsEnter.append("path");
      chordGroups.exit().remove();
      chordGroups = chordGroups.merge(chordGroupsEnter);

      // Add labels
      chordGroups
        .select("text")
          .each(function(d) {
            angle.set(this, (d.startAngle + d.endAngle) / 2);
            flip.set(this, angle.get(this) > Math.PI);
          })
          .attr("transform", function(d) {
            return [
              "rotate(" + (angle.get(this) / Math.PI * 180 - 90) + ")",
              "translate(" + (outerRadius + labelPadding) + ")",
              flip.get(this) ? "rotate(180)" : ""
            ].join("");
          })
          .attr("text-anchor", function(d) {
            return flip.get(this) ? "end" : "start";
          })
          .attr("alignment-baseline", "central")
          .text(function(d) {
            return matrix.names[d.index];
          });

      // Render the chord group arcs.
      chordGroups
        .select("path")
          .attr("d", arc)
          .style("fill", function(group) {
            return color(group.index);
          });

    }

    // Generates a matrix (2D array) from the given data, which is expected to
    // have fields {origin, destination, count}. The matrix data structure is required
    // for use with the D3 Chord layout.
    function generateMatrix(data){
      var indices = {},
          matrix = [],
          names = [],
          n = 0, i, j;

      function recordIndex(name){
        if( !(name in indices) ){
          indices[name] = n++;
          names.push(name);
        }
      }

      data.forEach(function (d){
        recordIndex(source(d));
        recordIndex(destination(d));
      });

      for(i = 0; i < n; i++){
        matrix.push([]);
        for(j = 0; j < n; j++){
          matrix[i].push(0);
        }
      }

      data.forEach(function (d){
        i = indices[source(d)];
        j = indices[destination(d)];
        matrix[i][j] = weight(d);
      });

      matrix.names = names;

      return matrix;
    }

    my.onSelectedRibbonChange = function (callback){
      onSelectedRibbonChangeCallback = callback;
    };

    my.selectedRibbon = function (){
      return selectedRibbon;
    };

    return my;
  }
});
<commit_msg>Refactor ribbon opacity function<commit_after>// This module defines a reusable Chord Diagram using D3.
define(function(require) {

  // Load D3 v4 via our custom bundle build output.
  var d3 = require("../d3/d3.min");

  // Constructor function for a Chord Diagram.
  // Accepts a container `div` DOM element.
  return function ChordDiagram(div){

    // Configuration parameters.
    var width = 500,
        height = 500,
        outerPadding = 50,
        arcThickness = 20,
        padAngle = 0.07,
        outerRadius = width / 2 - outerPadding,
        innerRadius = outerRadius - arcThickness,
        labelPadding = 10,
        defaultOpacity = 0.6,
        selectedRibbon = null,
        onSelectedRibbonChangeCallback = function (){};

    // These "column" variables represent keys in the row objects of the input table.
    // Kibana's `tabify` happens to give us column names "1", "2", and "3".
    // These correspond to the Schemas defined for the plugin.
    var chordWeightColumn = "1",
        chordSourceColumn = "2",
        chordDestinationColumn = "3";

    // Accessor functions for columns.
    var weight = function (d){ return d[chordWeightColumn]; },
        source = function (d){ return d[chordSourceColumn]; },
        destination = function (d){ return d[chordDestinationColumn]; };

    // D3 Local objects for DOM-local storage of label angles and
    // whether or not labels should be flipped upside-down.
    var angle = d3.local(),
        flip = d3.local();

    // DOM Elements.
    var svg = d3.select(div).append("svg")
          .attr("width", width)
          .attr("height", height),
        g = svg.append("g")
          .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"),
        ribbonsG = g.append("g"),
        chordGroupsG = g.append("g");

    // D3 layouts, shapes and scales.
    var ribbon = d3.ribbon()
          .radius(innerRadius),
        chord = d3.chord()
          .padAngle(padAngle),
        color = d3.scaleOrdinal()
          .range(d3.schemeCategory20),
        arc = d3.arc()
          .innerRadius(innerRadius)
          .outerRadius(outerRadius);

    // Renders the given data as a chord diagram.
    function my(data){

      // Pre-process the data and calculate the Chord Diagram layout.
      var matrix = generateMatrix(data),
          chords = chord(matrix);

      // Render the ribbons of the Chord Diagram (the connecting fibers inside the circle).
      var ribbons = ribbonsG
        .selectAll("path")
          .data(chords);
      ribbons = ribbons.enter().append("path").merge(ribbons);
      ribbons
        .attr("d", ribbon)
        .style("fill", function(d) {
          return color(d.source.index);
        })
        .style("stroke", "black")
        .style("stroke-opacity", 0.2)
        .call(setRibbonOpacity)
        .on("mousedown", function (d){
          selectedRibbon = {
            sourceIndex: d.source.index,
            targetIndex: d.target.index,
            source: matrix.names[d.source.index],
            destination: matrix.names[d.target.index]
          };
          onSelectedRibbonChangeCallback();
          setRibbonOpacity(ribbons);
        });
      ribbons.exit().remove();

      // Scaffold the chord groups.
      var chordGroups = chordGroupsG.selectAll("g").data(chords.groups);
      var chordGroupsEnter = chordGroups.enter().append("g");
      chordGroupsEnter.append("text");
      chordGroupsEnter.append("path");
      chordGroups.exit().remove();
      chordGroups = chordGroups.merge(chordGroupsEnter);

      // Add labels
      chordGroups
        .select("text")
          .each(function(d) {
            angle.set(this, (d.startAngle + d.endAngle) / 2);
            flip.set(this, angle.get(this) > Math.PI);
          })
          .attr("transform", function(d) {
            return [
              "rotate(" + (angle.get(this) / Math.PI * 180 - 90) + ")",
              "translate(" + (outerRadius + labelPadding) + ")",
              flip.get(this) ? "rotate(180)" : ""
            ].join("");
          })
          .attr("text-anchor", function(d) {
            return flip.get(this) ? "end" : "start";
          })
          .attr("alignment-baseline", "central")
          .text(function(d) {
            return matrix.names[d.index];
          });

      // Render the chord group arcs.
      chordGroups
        .select("path")
          .attr("d", arc)
          .style("fill", function(group) {
            return color(group.index);
          });

      // Sets the opacity values for all ribbons.
      function setRibbonOpacity(selection){
        selection.style("opacity", function (d){

          // If there is a currently selected ribbon,
          if(selectedRibbon){

            // show the selected chord in full color,
            if(
              (selectedRibbon.sourceIndex === d.source.index)
              &&
              (selectedRibbon.targetIndex === d.target.index)
            ){
              return defaultOpacity;
            } else {

              // and show all others faded out.
              return 0.1;
            }
          } else {

            // If there is no currently selected ribbon,
            // then show all ribbons with slight transparency.
            return defaultOpacity;
          }
        });
      }
    }

    // Generates a matrix (2D array) from the given data, which is expected to
    // have fields {origin, destination, count}. The matrix data structure is required
    // for use with the D3 Chord layout.
    function generateMatrix(data){
      var indices = {},
          matrix = [],
          names = [],
          n = 0, i, j;

      function recordIndex(name){
        if( !(name in indices) ){
          indices[name] = n++;
          names.push(name);
        }
      }

      data.forEach(function (d){
        recordIndex(source(d));
        recordIndex(destination(d));
      });

      for(i = 0; i < n; i++){
        matrix.push([]);
        for(j = 0; j < n; j++){
          matrix[i].push(0);
        }
      }

      data.forEach(function (d){
        i = indices[source(d)];
        j = indices[destination(d)];
        matrix[i][j] = weight(d);
      });

      matrix.names = names;

      return matrix;
    }

    my.onSelectedRibbonChange = function (callback){
      onSelectedRibbonChangeCallback = callback;
    };

    my.selectedRibbon = function (){
      return selectedRibbon;
    };

    return my;
  }
});
<|endoftext|>


<commit_before>/**
 * @module olcs.AutoRenderLoop
 */

class AutoRenderLoop {
  /**
   * @constructor
   * @param {olcs.OLCesium} ol3d
   */
  constructor(ol3d) {
    this.ol3d = ol3d;
    this.scene_ = ol3d.getCesiumScene();
    this.canvas_ = this.scene_.canvas;
    this._boundNotifyRepaintRequired = this.notifyRepaintRequired.bind(this);

    this.repaintEventNames_ = [
      'mousemove', 'mousedown', 'mouseup',
      'touchstart', 'touchend', 'touchmove',
      'pointerdown', 'pointerup', 'pointermove',
      'wheel'
    ];

    this.enable();
  }

  /**
   * Enable.
   */
  enable() {
    this.scene_.requestRenderMode = true;
    for (const repaintKey of this.repaintEventNames_) {
      this.canvas_.addEventListener(repaintKey, this._boundNotifyRepaintRequired, false);
    }

    window.addEventListener('resize', this._boundNotifyRepaintRequired, false);

    // Listen for changes on the layer group
    this.ol3d.getOlMap().getLayerGroup().on('change', this._boundNotifyRepaintRequired);
  }

  /**
   * Disable.
   */
  disable() {
    for (const repaintKey of this.repaintEventNames_) {
      this.canvas_.removeEventListener(repaintKey, this._boundNotifyRepaintRequired, false);
    }

    window.removeEventListener('resize', this._boundNotifyRepaintRequired, false);

    this.ol3d.getOlMap().getLayerGroup().un('change', this._boundNotifyRepaintRequired);
    this.scene_.requestRenderMode = false;
  }

  /**
   * Restart render loop.
   * Force a restart of the render loop.
   * @api
   */
  restartRenderLoop() {
    this.notifyRepaintRequired();
  }

  notifyRepaintRequired() {
    this.scene_.requestRender();
  }
}

export default AutoRenderLoop;
<commit_msg>Fix Cesium requestRenderMode usage<commit_after>/**
 * @module olcs.AutoRenderLoop
 */

class AutoRenderLoop {
  /**
   * @constructor
   * @param {olcs.OLCesium} ol3d
   */
  constructor(ol3d) {
    this.ol3d = ol3d;
    this.scene_ = ol3d.getCesiumScene();
    this.canvas_ = this.scene_.canvas;
    this._boundNotifyRepaintRequired = this.notifyRepaintRequired.bind(this);

    this.repaintEventNames_ = [
      'mousemove', 'mousedown', 'mouseup',
      'touchstart', 'touchend', 'touchmove',
      'pointerdown', 'pointerup', 'pointermove',
      'wheel'
    ];

    this.enable();
  }

  /**
   * Enable.
   */
  enable() {
    this.scene_.requestRenderMode = true;
    this.scene_.maximumRenderTimeChange = 1000;
    for (const repaintKey of this.repaintEventNames_) {
      this.canvas_.addEventListener(repaintKey, this._boundNotifyRepaintRequired, false);
    }

    window.addEventListener('resize', this._boundNotifyRepaintRequired, false);

    // Listen for changes on the layer group
    this.ol3d.getOlMap().getLayerGroup().on('change', this._boundNotifyRepaintRequired);
  }

  /**
   * Disable.
   */
  disable() {
    for (const repaintKey of this.repaintEventNames_) {
      this.canvas_.removeEventListener(repaintKey, this._boundNotifyRepaintRequired, false);
    }

    window.removeEventListener('resize', this._boundNotifyRepaintRequired, false);

    this.ol3d.getOlMap().getLayerGroup().un('change', this._boundNotifyRepaintRequired);
    this.scene_.requestRenderMode = false;
  }

  /**
   * Restart render loop.
   * Force a restart of the render loop.
   * @api
   */
  restartRenderLoop() {
    this.notifyRepaintRequired();
  }

  notifyRepaintRequired() {
    this.scene_.requestRender();
  }
}

export default AutoRenderLoop;
<|endoftext|>


<commit_before>/*
Copyright [2009-2014] EMBL-European Bioinformatics Institute
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// RNAcentral metasearch app.

;

/**
 * Make it possible to include underscore as a dependency.
 */
var underscore = angular.module('underscore', []);
underscore.factory('_', function() {
    return window._;
});

/**
 * Create RNAcentral app.
 */
var rnaMetasearch = angular.module('rnacentralApp', ['chieffancypants.loadingBar', 'underscore']);

/**
 * html5mode removes hashtags from urls.
 */
rnaMetasearch.config(['$locationProvider', function($locationProvider) {
    $locationProvider.html5Mode(true);
}]);

/**
 * Service for passing data between controllers.
 */
rnaMetasearch.service('results', ['_', function(_) {
    var result = {
        hits: null,
        rnas: [],
        facets: []
    }
    var show_results = false;

    /**
     * Process deeply nested json objects like this:
     * { "field" : [ {"id": "description", "values": {"value": "description_value"}} ] }
     * into key-value pairs like this:
     * {"description": "description_value"}
     */
    function preprocess_results(data) {
        result.hits = data.result.hitCount;

        result.facets = _.each(data.result.facets.facet, function(facet){
            facet.facetValues.facetValue = wrap_in_array(facet.facetValues.facetValue);
            return facet;
        });

        data.result.entries.entry = wrap_in_array(data.result.entries.entry);
        result.rnas = _.each(data.result.entries.entry, function(entry){
            // flatten deeply nested arrays
            _.each(entry.fields.field, function(field){
                entry[field['@id']] = field.values.value;
            });
        });

        function wrap_in_array(data) {
            // wrap single entry in an array
            if ( !_.isArray(data) ) {
                return [data]
            } else {
                return data;
            }
        };
    };

    return {
        get_status: function() {
            return show_results;
        },
        set_status: function() {
            show_results = true;
        },
        save_results: function(data) {
            preprocess_results(data);
            console.log(result);
        },
        get_results: function() {
            return result;
        }
    }
}]);

rnaMetasearch.controller('MainContent', ['$scope', '$anchorScroll', '$location', 'results', function($scope, $anchorScroll, $location, results) {
    /**
     * Enables scrolling to anchor tags.
     * Title
     */
    $scope.scrollTo = function(id) {
        $location.hash(id);
        $anchorScroll();
    };

    $scope.$watch(function () { return results.get_status(); }, function (newValue, oldValue) {
        if (newValue != null) {
            $scope.show_results = newValue;
        }
    });
}]);

rnaMetasearch.controller('ResultsListCtrl', ['$scope', 'results', function($scope, results) {

    $scope.result = results.get_results();
    $scope.show_results = results.get_status();

    $scope.$watch(function () { return results.get_results(); }, function (newValue, oldValue) {
        if (newValue != null) {
            $scope.result = newValue;
        }
    });

    $scope.$watch(function () { return results.get_status(); }, function (newValue, oldValue) {
        if (newValue != null) {
            $scope.show_results = newValue;
        }
    });

}]);

rnaMetasearch.controller('QueryCtrl', ['$scope', '$http', '$location', 'results', function($scope, $http, $location, results) {

    $scope.query = {
        text: '',
        submitted: false
    };
    $scope.show_results = false;
    $scope.save_results = results.save_results;
    $scope.set_status = results.set_status;

    var search_config = {
        ebeye_base_url: 'http://ash-4.ebi.ac.uk:8080',
        rnacentral_base_url: 'http://localhost:8000',
        fields: ['description', 'active', 'length', 'name'],
        facetfields: ['active', 'expert_db', 'TAXONOMY'],
        facetcount: 10
    };

    var query_urls = {
        'ebeye_search': search_config.ebeye_base_url +
                        '/ebisearch/ws/rest/rnacentral' +
                        '?query={QUERY}' +
                        '&format=json' +
                        '&fields=' + search_config.fields.join() +
                        '&facetcount=' + search_config.facetcount +
                        '&facetfields=' + search_config.facetfields.join(),
        'proxy': search_config.rnacentral_base_url +
                 '/api/internal/ebeye?url={EBEYE_URL}'
    };

    // watch url changes to perform a new search
    $scope.$watch(function () { return $location.url(); }, function (newUrl, oldUrl) {

         // a regular non-search url, potentially unchanged
        if (newUrl !== oldUrl) {
            if (newUrl.indexOf('/search') == -1) {
                // not a search page, redirect
                window.location.href = newUrl;
            } else {
                // a search result page, launch a new search
                $scope.query.text = $location.search().q;
                $scope.search($location.search().q);
            }
        }
    });

    $scope.search = function(query) {
        $scope.query.text = query;
        $scope.show_results = true;
        $scope.set_status();

        var ebeye_url = query_urls.ebeye_search.replace('{QUERY}', query);
        var url = query_urls.proxy.replace('{EBEYE_URL}', encodeURIComponent(ebeye_url));

        $http({
            url: url,
            method: 'GET'
        }).success(function(data) {
            $scope.save_results(data);
            $scope.query.submitted = false;
        });
    }

    $scope.trigger_search = function(query) {
        $location.url('/search' + '?q=' + query);
    }

    $scope.submit_query = function() {
        $scope.query.submitted = true;
        if ($scope.queryForm.text.$invalid) {
            return;
        }
        $scope.trigger_search($scope.query.text);
    };

    var check_if_search_url = function () {
       // check if there is query in url
        if ($location.url().indexOf("/search?q=") > -1) {
            // a search result page, launch a new search
            $scope.query.text = $location.search().q;
            $scope.search($location.search().q);
        }
    };

    // run once at initialisation
    check_if_search_url();
}]);
<commit_msg>Add rna_type facet<commit_after>/*
Copyright [2009-2014] EMBL-European Bioinformatics Institute
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// RNAcentral metasearch app.

;

/**
 * Make it possible to include underscore as a dependency.
 */
var underscore = angular.module('underscore', []);
underscore.factory('_', function() {
    return window._;
});

/**
 * Create RNAcentral app.
 */
var rnaMetasearch = angular.module('rnacentralApp', ['chieffancypants.loadingBar', 'underscore']);

/**
 * html5mode removes hashtags from urls.
 */
rnaMetasearch.config(['$locationProvider', function($locationProvider) {
    $locationProvider.html5Mode(true);
}]);

/**
 * Service for passing data between controllers.
 */
rnaMetasearch.service('results', ['_', function(_) {
    var result = {
        hits: null,
        rnas: [],
        facets: []
    }
    var show_results = false;

    /**
     * Process deeply nested json objects like this:
     * { "field" : [ {"id": "description", "values": {"value": "description_value"}} ] }
     * into key-value pairs like this:
     * {"description": "description_value"}
     */
    function preprocess_results(data) {
        result.hits = data.result.hitCount;

        result.facets = _.each(data.result.facets.facet, function(facet){
            facet.facetValues.facetValue = wrap_in_array(facet.facetValues.facetValue);
            return facet;
        });

        data.result.entries.entry = wrap_in_array(data.result.entries.entry);
        result.rnas = _.each(data.result.entries.entry, function(entry){
            // flatten deeply nested arrays
            _.each(entry.fields.field, function(field){
                entry[field['@id']] = field.values.value;
            });
        });

        function wrap_in_array(data) {
            // wrap single entry in an array
            if ( !_.isArray(data) ) {
                return [data]
            } else {
                return data;
            }
        };
    };

    return {
        get_status: function() {
            return show_results;
        },
        set_status: function() {
            show_results = true;
        },
        save_results: function(data) {
            preprocess_results(data);
            console.log(result);
        },
        get_results: function() {
            return result;
        }
    }
}]);

rnaMetasearch.controller('MainContent', ['$scope', '$anchorScroll', '$location', 'results', function($scope, $anchorScroll, $location, results) {
    /**
     * Enables scrolling to anchor tags.
     * Title
     */
    $scope.scrollTo = function(id) {
        $location.hash(id);
        $anchorScroll();
    };

    $scope.$watch(function () { return results.get_status(); }, function (newValue, oldValue) {
        if (newValue != null) {
            $scope.show_results = newValue;
        }
    });
}]);

rnaMetasearch.controller('ResultsListCtrl', ['$scope', 'results', function($scope, results) {

    $scope.result = results.get_results();
    $scope.show_results = results.get_status();

    $scope.$watch(function () { return results.get_results(); }, function (newValue, oldValue) {
        if (newValue != null) {
            $scope.result = newValue;
        }
    });

    $scope.$watch(function () { return results.get_status(); }, function (newValue, oldValue) {
        if (newValue != null) {
            $scope.show_results = newValue;
        }
    });

}]);

rnaMetasearch.controller('QueryCtrl', ['$scope', '$http', '$location', 'results', function($scope, $http, $location, results) {

    $scope.query = {
        text: '',
        submitted: false
    };
    $scope.show_results = false;
    $scope.save_results = results.save_results;
    $scope.set_status = results.set_status;

    var search_config = {
        ebeye_base_url: 'http://ash-4.ebi.ac.uk:8080',
        rnacentral_base_url: 'http://localhost:8000',
        fields: ['description', 'active', 'length', 'name'],
        facetfields: ['active', 'expert_db', 'rna_type', 'TAXONOMY'],
        facetcount: 10
    };

    var query_urls = {
        'ebeye_search': search_config.ebeye_base_url +
                        '/ebisearch/ws/rest/rnacentral' +
                        '?query={QUERY}' +
                        '&format=json' +
                        '&fields=' + search_config.fields.join() +
                        '&facetcount=' + search_config.facetcount +
                        '&facetfields=' + search_config.facetfields.join(),
        'proxy': search_config.rnacentral_base_url +
                 '/api/internal/ebeye?url={EBEYE_URL}'
    };

    // watch url changes to perform a new search
    $scope.$watch(function () { return $location.url(); }, function (newUrl, oldUrl) {

         // a regular non-search url, potentially unchanged
        if (newUrl !== oldUrl) {
            if (newUrl.indexOf('/search') == -1) {
                // not a search page, redirect
                window.location.href = newUrl;
            } else {
                // a search result page, launch a new search
                $scope.query.text = $location.search().q;
                $scope.search($location.search().q);
            }
        }
    });

    $scope.search = function(query) {
        $scope.query.text = query;
        $scope.show_results = true;
        $scope.set_status();

        var ebeye_url = query_urls.ebeye_search.replace('{QUERY}', query);
        var url = query_urls.proxy.replace('{EBEYE_URL}', encodeURIComponent(ebeye_url));

        $http({
            url: url,
            method: 'GET'
        }).success(function(data) {
            $scope.save_results(data);
            $scope.query.submitted = false;
        });
    }

    $scope.trigger_search = function(query) {
        $location.url('/search' + '?q=' + query);
    }

    $scope.submit_query = function() {
        $scope.query.submitted = true;
        if ($scope.queryForm.text.$invalid) {
            return;
        }
        $scope.trigger_search($scope.query.text);
    };

    var check_if_search_url = function () {
       // check if there is query in url
        if ($location.url().indexOf("/search?q=") > -1) {
            // a search result page, launch a new search
            $scope.query.text = $location.search().q;
            $scope.search($location.search().q);
        }
    };

    // run once at initialisation
    check_if_search_url();
}]);
<|endoftext|>


<commit_before>(function() {

    function Expectation(name) {
        Object.defineProperty(this, 'name', {
            get: function() {return name;}
        });
        Error.apply(this, Array.prototype.slice.call(arguments))
    }
    Expectation.prototype = Object.create(Error.prototype)
    Expectation.prototype.toString = function expectationString() {
        return '<Expectation of ' + this.name +'>'
    }

    function Argument(value) {
        Object.defineProperty(this, 'value', {
            get: function() { return name; }
        });

    }

    function AsyncExecutionException(){}

    function DependencyGraph(
            syncGetters, asyncGetters, callerArguments, job) {
        this.asyncGetters = asyncGetters;
        this.syncGetters = syncGetters;

        this.callerArguments = callerArguments instanceof Array
            ? callerArguments
            : [];

        this.job = job;
    }

    var Constructor = function State(host, graph /* instanceof DependencyGraph */,
            args /* array: [async [, arguments ... ], callback, errrback] */) {
        this._host = host;
        this._graph = graph;

        this._async = args[0];
        this._args = args.slice(1);
        // FIXME: in case we'll support returning a promise this will need
        // an adjustment here
        this._errback = this._async
            ? this.args.pop()
            : undefined;
        this._callback = this._async
            ? this.args.pop()
            : undefined;

        this._obtained = {};
        for(;i<this._graph.callerArguments.length; i++)
            this._obtained[this._graph.callerArguments[i]] = this._args[i];
        this._args.unshift(this._obtainAPI.bind(this));

        // let's see if this will stay like it is
        this._missingExpectations = []
    }

    // Constructor.prototype = Object.create(null);
    var p = Constructor.prototype;

    p._obtain =  function _obtain(key) {
        // so we need to obtain a key
        // this should build a topological sorting as a starting point

        var func, args, dependencies, dependents = {};

        dependent = this._graph.asyncGetters[key];
        args = this._graph.asyncGetters[key].slice(1);

        // see prepareEvaluation of dependencyGraph.js to get
        // dependencyCount and dependents and start from there

        // dependents with count of unresolved dependencies
        // when count of unresolved dependencies === 0 the dependent
        // can be executed

        // this must exclude all already known dependencies,
        // like in callerArguments (everything that is already obtained)
        // or special args _callback, _errback, _obtain(?), etc.
        // AND all args that are the value to be passed itself.
        // Expectations are everything where typof x === 'string'
        // e.G. strings created with the string literal.
        // to pass a string as string argument use new String('value')
        // so typeof x will be 'object'

        // FIXME: for easier development I use the not cleaned args yet
        dependencies = args
        dependents[dependent] = dependencies.length;

        // record who is dependent of these dependencis
        // so when the dependency is resolved we can do:
        // for(var i=0; i<depandantOf[dependency].length; i++){
        //     dependent = depandantOf[dependency][i];
        //     dependents[dependent] -= 1;
        //     if(dependents[dependent] === 0)
        //        and if it is not beeing executed at the moment!
        //        and if it is not already resolved!
        //        both last points are unlikeley, because this is
        //        the single point where the method is exexuted, when
        //        all dependencies are resolved
        //
        //        execute(dependent);
        //  }
        for(var i=0; i<dependencies.length; i++) {
            if(!(dependencies[i] in depandantOf))
                depandantOf[dependencies[i]] = [];
            depandantOf[dependencies[i]].push(dependent);
        }

        for(var i=0; i<dependencies.length; i++) {
            var dependent2 = dependencies[i];
            var dependancies2 = this._graph.asyncGetters[i].slice(1);
        }

        var dependents = {

        }

        // dependencyCount and dependentsOf is what we want to create:
    }

    p._obtainAPI = function _obtainAPI(key) {
        var result;
        if (key in this._obtained)
            return this._obtained[key];

        try {
            result = this._obtain(key);
        }
        catch(e) {
            if(e instanceof AsyncExecutionException)
                 this._missingExpectations.push(key);
            // we re raise it in any case
            throw e;
        }
        // still here, so key is not missing anymore
        return result;
    }

    //
    p._resolve = function resolve() {
        // this threw an expectation in the previous version
        // that might be not needed any more
        // however job is expected to call _obrainAPI zero or more times
        return this.graph.job.call(this._host, this._args);
    }

    p.execute = function execute () {
        var result;
        try {
            result = this._resolve();
        }
        catch(e) {
            if(e instanceof AsyncExecutionException) {
                console.log('AsyncExecutionException');
                // the receiver will call execute again
                return;
            }

            if(!this._async)
                throw e;
            this._errback(e);
            return;
        }

        // got a result
        if(!this._async)
            return result;
        this._callback(result);
    }

    function executionEnvironmentFactory(syncGetters, asyncGetters,
            callerArguments, job) {
        var graph = new DependencyGraph(syncGetters, asyncGetters,
                                        callerArguments, job);
        return function runner( /* async, [ ... arguments, ] callback, errrback */) {
            // 'this' is the host
            var state = new State(this, graph, Array.prototype.slice.call(arguments));
            return state.execute();
        }
    }

    return executionEnvironmentFactory;
})()
<commit_msg>more business logic omitting yet the argument preparation<commit_after>(function() {

    function NotImplementedError(){}

    function Expectation(name) {
        Object.defineProperty(this, 'name', {
            get: function() {return name;}
        });
        Error.apply(this, Array.prototype.slice.call(arguments))
    }

    Expectation.prototype = Object.create(Error.prototype)
    Expectation.prototype.toString = function expectationString() {
        return '<Expectation ' + this.name +'>'
    }

    Expectation.prototype.toString = function expectationString() {
        return '<Expectation ' + this.name +'>'
    }

    function Argument(value) {
        Object.defineProperty(this, 'value', {
            get: function() { return name; }
        });
    }
    Argument.prototype.toString = function argumentString() {
        return '<Argument ' + this.name +'>'
    }

    function Dependency(value) {
        Object.defineProperty(this, 'value', {
            get: function() { return name; }
        });
    }
    Dependency.prototype.toString = function dString() {
        return '<Dependency ' + this.name +'>'
    }

    function AsyncExecutionException(){}

    function AssertionFailed(){
        Error.apply(this, Array.prototype.slice.call(arguments))
    }
    AssertionFailed.prototype = Object.create(Error.prototype)

    function assert(test, message) {
        if(!test)
            throw new AssertionFailed(mesage);
    }

    function DependencyGraph(
            syncGetters, asyncGetters, callerArguments, job) {
        this.asyncGetters = asyncGetters;
        this.syncGetters = syncGetters;

        this.callerArguments = callerArguments instanceof Array
            ? callerArguments
            : [];

        this.job = job;
    }
     var _DGp = DependencyGraph.prototype;

    var Constructor = function State(host, graph /* instanceof DependencyGraph */,
            args /* array: [async [, arguments ... ], callback, errrback] */) {
        this._host = host;
        this._graph = graph;

        this._async = args[0];
        this._args = args.slice(1);
        // FIXME: in case we'll support returning a promise this will need
        // an adjustment here
        this._errback = this._async
            ? this._args.pop()
            : undefined;
        this._callback = this._async
            ? this._args.pop()
            : undefined;

        this._obtained = {};

        for(;i<this._graph.callerArguments.length; i++)
            this._obtained[this._graph.callerArguments[i]] = this._args[i];
        this._args.unshift(this._obtainAPI.bind(this));

        this._waitingFor = {}
        this._waitingCount = 0
    }

    // Constructor.prototype = Object.create(null);
    var p = Constructor.prototype;

    p.addWaitingAsync = function(dependency) {
        assert(!(dependency in this._waitingFor), 'Already waiting for '
                                                  + dependency)
        // Add a time when waiting started, or a setTimeout for
        // timeout control or such?
        this._waitingFor[dependency] = null;
        this._waitingCount += 1;
        return this._waitingCount
    }

    p.removeWaitingAsync = function(dependency) {
        assert(dependency in this._waitingFor, 'Not waiting for '
                                                  + dependency)
        delete this._waitingFor[dependency];
        this._waitingCount -= 1;
        return this._waitingCount;
    }

    // this._dependencyCallback.bind(this, dependency)
    p._dependencyCallback(dependency, result) {
        this._obtained[dependency.name] = result
        this._resolve(dependency)
        if(!this._waitingFor.length)
            // that's it, nothing to do anymore
            this.execute();
    }

    p._call = function(dependency) {
        throw new NotImplementedError();

        var args = this._getArgs(dependency)
          , func = this._getFunc(dependency)
          ;
        if(dependency.isAsync) {
            this._waitingFor.push(dependency)
            func.apply(this._host, args)
        }
        else
            this._obtained[dependency.name] = func.apply(this._host, args)
    }

    /**
     * Executes all dependencies given by the arguments.
     * First execute all asynchronous dependencies, as these probably
     * dispatch to a server or such and run nonblocking (hopefully,
     * where is the point otherwise?)
     */
    p._resolve = function(/* dependency, ... */) {
        var resolved = Array.prototype.call(arguments)
          , sosd = [] // stack of synchconous dependencies
          , i
          , dependency
          , dependents = this._dependents
          , dependencyCounters = this._dependencyCounters
          , dependent
          ;

        // IMPORTANT! The logical or || will execute dependency = sosd.pop()
        // only if the first expression returns something false.
        // so we first we _call all async dependencies and then the sync
        // ones. This is done, because we wan't to dispatch the async requests
        // ASAP.
        while((dependency = resolved.pop()) || dependency = sosd.pop()) {
            if(dependency.isAsync)
                this._call(dependency)
            else if(resolved.length === 0)
                // execute after all async dependencies have been called
                sosd.push(k)
            else {
                // execute the sync dependency
                this._call(dependency);
                for(i=0; i<dependents[dependency.name].length;i++) {
                    dependent = dependents[dependency.name][i]
                    dependencyCounters[dependent] -= 1
                    if (dependencyCounters[dependent] === 0)
                        // might be async or sync
                        resolved.push(this._graph.get(dependent))
                }
            }
        }
    }

    p._obtain =  function _obtain(key) {
        throw new NotImplementedError();

        assert(!(key in this._obtained), 'Key "'+ key +'" must not be '
                                        + 'in this._obtained, but it is.')

        // so we need to obtain a key
        // this should build a topological sorting as a starting point

        var dependentMethod, dependentArgs, dependencies, dependents = {};

        dependentMethod = this._graph.asyncGetters[key][0];
        dependentArgs = this._graph.asyncGetters[key].slice(1);

        // dependents with count of unresolved dependencies
        // when count of unresolved dependencies === 0 the dependent
        // can be executed

        // this must exclude all already known dependencies,
        // like in callerArguments (everything that is already obtained)
        // or special args _callback, _errback, _obtain(?), etc.
        //   I'm noy shure if this is true, if it's already obtained we'll
        //   detect it later and callerArguments are like dependencies without
        //   own dependencies

        // AND all args that are no Expectations but the value to be passed
        //itself: Expectations are everything where typof x === 'string'
        // e.G. strings created with the string literal. to pass a string
        // as string argument use new String('value') so typeof x will be
        // 'object' or use the Argument constructor of this package:
        // new Argument('value')

        // FIXME: for easier development I use the not cleaned args yet
        dependencies = dependentArgs
        // TODO:
        // this.dependencyCounters, this.dependents = prepareEvaluation(key, dependencies)
        // see prepareEvaluation of dependencyGraph.js to get
        // dependencyCount and dependents and start from there

        // Every dependency with a count of 0 can be executed immediately
        var resolved = []
          , k
          ;
        for(k in dependencyCounters) {
            if (dependencyCounters[k] === 0)
                resolved.push(this._graph.get(k));
        }

        this._resolve.apply(this, resolved)
        // if there wasn't any async dependency we should have a result
        // by now. So we should return it.
        if(this._waitingFor.length)
            throw new AsyncExecutionException()
        // it has to be in this._obtained now
    }

    p._obtainAPI = function _obtainAPI(key) {
        if (!(key in this._obtained))
            // will raise AsyncExecutionException when needed
            this._obtain(key);
        assert(key in this._obtained, 'Key "'+ key +'" must be in '
                                    + 'this._obtained, but it isn\'t.')
        return this._obtained[key];
    }

    p.execute = function execute () {
        var result;
        try {
            // job is expected to call _obtainAPI zero or more times
            result = this.graph.job.call(this._host, this._args);
        }
        catch(e) {
            if(e instanceof AsyncExecutionException) {
                console.log('AsyncExecutionException');
                // the receiver will call execute again
                return;
            }

            if(!this._async)
                throw e;
            this._errback(e);
            return;
        }

        // got a result
        if(!this._async)
            return result;
        this._callback(result);
    }

    function executionEnvironmentFactory(syncGetters, asyncGetters,
            callerArguments, job) {
        var graph = new DependencyGraph(syncGetters, asyncGetters,
                                        callerArguments, job);
        return function runner( /* async, [ ... arguments, ] callback, errrback */) {
            // 'this' is the host
            var state = new State(this, graph, Array.prototype.slice.call(arguments));

            // There needs to be an assurance that an async job is
            // deferred (asynchronously) at least once! Theoretically,
            // since a developer might not expect stuff like an immediate
            // execution when calling an async method and since we have
            // side effects in javascript ASAP is just right.
            // Without side effects it would be better to wait and see
            // if an extra decoupling is needed (when there was no async
            // execution after resolving)
            // FIXME: Think about doing this smarter.
            // FIXME: When promises are aded to the API this needs reflection
            // here, too.
            if (async)
                setTimeout(state.execute.bind(state), 0)
            else
                return state.execute();
        }
    }

    return executionEnvironmentFactory;
})()
<|endoftext|>


<commit_before>/*
 === octapushJS ===
 Author  : Fadhly Permata
 eMail   : - fadhly.permata@gmail.com
 URL     : www.octapush.com

 === Credits ===
 Prime Developer : Fadhly Permata

 === Contributors ===
 ... just type your name here after editing the script ...
 */

/**
 * TODO:
 * - ADD AJAX
 */

(function (w) {
    'use strict';
    const version = '1.7.02.13';

    var _o_ = {
        /**
         * @desc Buffer for localization storage.
         */
        localization: {},
        /**
         * @desc Returning octapushJS core version.
         */
        version: version,
        /**
         * @desc Generate octapush string art.
         * @param {string} char1 [default value is space character]
         * @param {string} char2 [default value is equal character]
         * @returns {string} octapush string art
         */
        copyleft: function (char1, char2) {
            var artPattern = [
                [0x5c],
                [0x1e, 0x3, 0x33, 0x3, 0x5],
                [0x1e, 0x3, 0x33, 0x3, 0x5],
                [0xa, 0x5, 0x6, 0x5, 0x3, 0x3, 0x8, 0x6, 0x3, 0x2, 0x1, 0x5, 0x3, 0x3, 0x3, 0x3, 0x5, 0x5, 0x4, 0x3, 0x6],
                [0x7, 0x3, 0x2, 0x3, 0x3, 0x3, 0x2, 0x3, 0x2, 0x7, 0x3, 0x3, 0x2, 0x3, 0x3, 0x3, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x2, 0x3, 0x3, 0x3, 0x2, 0x3, 0x1, 0x4, 0x2],
                [0x5, 0x3, 0x3, 0x3, 0x2, 0x3, 0x8, 0x6, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x2, 0x3, 0x3, 0x3, 0x2, 0x5, 0x6, 0x3, 0x3, 0x3, 0x2],
                [0x4, 0x3, 0x3, 0x3, 0x2, 0x3, 0x8, 0x3, 0x6, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x2, 0x3, 0x3, 0x3, 0x3, 0x6, 0x4, 0x3, 0x3, 0x3, 0x3],
                [0x3, 0x3, 0x3, 0x3, 0x2, 0x3, 0x3, 0x3, 0x2, 0x3, 0x6, 0x3, 0x3, 0x3, 0x3, 0x3, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x5, 0x5, 0x3, 0x3, 0x3, 0x3, 0x4],
                [0x2, 0x3, 0x2, 0x3, 0x3, 0x3, 0x2, 0x3, 0x3, 0x7, 0x2, 0x3, 0x2, 0x1, 0x1, 0x2, 0x3, 0x3, 0x1, 0x3, 0x4, 0x3, 0x3, 0x3, 0x2, 0x3, 0x3, 0x3, 0x2, 0x3, 0x3, 0x3, 0x5],
                [0x2, 0x5, 0x6, 0x5, 0x6, 0x5, 0x4, 0x4, 0x2, 0x3, 0x2, 0x5, 0x7, 0x5, 0x1, 0x3, 0x3, 0x5, 0x4, 0x3, 0x4, 0x3, 0x5],
                [0x2b, 0x3, 0x2e],
                [0x2b, 0x3, 0x2e],
                [0x5c]
            ];

            var patternChars = [_o_.utility.ifNull(char1, ' '), _o_.utility.ifNull(char2, '=')];
            var str = '';

            artPattern.forEach(function (el, idx, arr) {
                var charMode = true;
                str += '\n';

                el.forEach(function (el, idx, arr) {
                    charMode = !charMode;
                    str += new Array(el + 1).join(patternChars[charMode ? 0x1 : 0x0]);
                });
            });

            return str;
        },
        /**
         * @desc Display octapush string art on browser console.
         */
        showCopyleft: function () {
            console.log(_o_.copyleft());
        },
        compare: {
            /**
             * @desc Check the object is null or not
             * @param {any} obj
             * @returns {bool} returning TRUE if object is null or '' or undefined or array with length 0. Otherwise is FALSE.
             */
            isNullOrEmpty: function (obj) {
                return _o_.compare.isUndefined(obj) || null === obj || '' === obj || 0x0 === obj.length;
            },
            /**
             * @desc Check the `obj` is having properties or not.
             * @param {object} obj
             * @returns {bool} returning TRUE if there are no properties inside `obj`. Otherwise is FALSE.
             */
            isEmpty: function (obj) {
                for (name in obj) return false;
                return true;
            },
            /**
             * @desc Check the `obj` is undefined or not.
             * @param {any} obj
             * @returns {bool} return TRUE if the `obj` is UNDEFINED. Otherwise is FALSE.
             */
            isUndefined: function (obj) {
                return obj === undefined;
            },
            /**
             * @desc Check the `obj` is defined or not.
             * @param {any} obj
             * @returns {bool} return TRUE if the `obj` is DEFINED. Otherwise is FALSE.
             */
            isDefined: function (obj) {
                return obj !== undefined;
            },
            /**
             * @desc Check the `obj` is a number or not.
             * @param {any} obj
             * @returns {bool} return FALSE if the `obj` is NUMBER. Otherwise is TRUE.
             */
            isNan: function (obj) {
                return isNaN(obj);
            },
            /**
             * @desc Check the `obj` is a number or not
             * @param {any} obj
             * @returns {bool} return TRUE if the `obj` is NUMBER. Otherwise is FALSE.
             */
            isNumber: function (obj) {
                return !isNaN(obj);
            },
            /**
             * @desc Check the `obj` is a JS function or not.
             * @param {any} obj
             * @returns {bool} return TRUE if the `obj` is a JS function. Otherwise is FALSE.
             */
            isFunction: function (obj) {
                return obj instanceof Function || _o_.utility.getType(obj) === 'function' || obj.__proto__.toString() === 'function () {\n}';
            },
            /**
             * @desc Check the `obj` is a string or not.
             * @param {any} obj
             * @returns {bool} return TRUE if the `obj` is a string. Otherwise is FALSE.
             */
            isString: function (obj) {
                return obj.__proto__ === '[object String]' || _o_.utility.getType(obj) === 'string';
            },
            /**
             * @desc Check the `obj` is a BOOLEAN or not.
             * @param {any} obj
             * @returns {bool} return TRUE if the `obj` is a boolean. Otherwise is FALSE.
             */
            isBool: function (obj) {
                return obj === true || obj === false || obj.__proto__ === '[object Boolean]' || _o_.utility.getType(obj) === 'boolean';
            },
            /**
             * @desc Check the `obj` is an array or not.
             * @param {any} obj
             * @returns {bool} return TRUE if the `obj` is an array. Otherwise is FALSE.
             */
            isArray: function (obj) {
                return _o_.compare.isUndefined(obj) ? false : Array.isArray(obj);
            },
            /**
             * @desc Check the `obj` is an integer or not.
             * @param {any} obj
             * @returns {bool} return TRUE if the `obj` is an integer. Otherwise is FALSE.
             */
            isInteger: function (obj) {
                return obj % 0x1 === 0x0;
            },
            isFloat: function (obj) {
                return Number(obj) === obj && obj % 1 !== 0;
            },
            isJsonObject: function (obj) {
                return _o_.compare.isUndefined(obj) ?
                    false :
                    (
                        _o_.compare.isFunction(obj) ?
                        false : {}.constructor === obj.constructor
                    )
            }
        },
        utility: {
            /**
             * @desc Getting the `obj` data type
             * @param {any} obj
             * @returns {string} Data type name;
             */
            getType: function (obj) {
                return !arguments ? null : (null === obj ? obj + '' : (typeof obj).toString());
            },
            /**
             * @desc Don't do anything
             */
            noop: function () {},
            /**
             * @desc Don't do anything
             */
            noOperation: function () {},
            /**
             * @desc Give the `defaultValue` if `obj` is NULL. Otherwise, give the `obj` value.
             * @param {any} obj
             * @param {any} defaultValue
             * @returns {any}  Return the `defaultValue` if `obj` is NULL. Otherwise, give the `obj` value.
             */
            ifNull: function (obj, defaultValue) {
                return _o_.compare.isNullOrEmpty(arguments) ? null : (!_o_.compare.isNullOrEmpty(obj) ? obj : defaultValue);
            },
            /**
             * @desc Execute `func` for `nTime`. In other word, this is simplify for JS native looping.
             * @param {Number} nTime
             * @param {Func} func
             * @returns -
             */
            loop: function (nTime, func) {
                if (!func) return;

                for (var i = 0x0; i < nTime; i++) {
                    if (func && _o_.compare.isFunction(func))
                        func(i);
                }
            },
            each: function (obj, callback, flattenNested) {
                if (_o_.compare.isNullOrEmpty(arguments) || _o_.compare.isNullOrEmpty(obj))
                    return;

                flattenNested = _o_.utility.ifNull(flattenNested, false);

                if (_o_.compare.isArray(obj)) {
                    obj.forEach(function (val, key, arr) {
                        if (_o_.compare.isArray(val) || _o_.compare.isJsonObject(val)) {
                            if (flattenNested)
                                _o_.utility.each(val, callback, flattenNested);
                            else
                            if (callback) callback(key, val);

                        } else {
                            if (callback) callback(key, val);
                        }
                    });

                } else if (_o_.compare.isJsonObject(obj)) {
                    for (var key in obj) {
                        if (_o_.compare.isArray(obj[key]) || _o_.compare.isJsonObject(obj[key])) {
                            if (flattenNested)
                                _o_.utility.each(obj[key], callback, flattenNested);
                            else
                            if (callback) callback(key, obj[key]);

                        } else {
                            if (callback) callback(key, obj[key]);
                        }
                    }
                }
            },
            forEach: function (obj, callback) {
                _o_.utility.each(obj, callback)
            }
        }
    };

    _o_.showCopyleft();
    w.octapushJS = w._o_ = _o_;
})(window);<commit_msg>- reformat inline documentation - reformat script - enhance each() & forEach() method like jquery each method - Add extend() method<commit_after>/*
 === octapushJS ===
 Author  : Fadhly Permata
 eMail   : fadhly.permata@gmail.com
 URL     : www.octapush.com

 === Credits ===
 Prime Developer : Fadhly Permata

 === Contributors ===
 ... just type your name here after editing the script ...
 */

/**
 * TODO:
 * - ADD AJAX
 * - REVISE utility.extend to support deep merging;
 */

(function (w) {
    'use strict';
    const version = '1.7.02.17';

    var _o_ = {
        /**
         * @desc Buffer for localization storage.
         */
        localization: {},
        /**
         * @desc Returning octapushJS core version.
         */
        version: version,
        /**
         * @desc Generate octapush string art.
         * @param {string} char1 [default value is space character]
         * @param {string} char2 [default value is equal character]
         * @returns {string} octapush string art
         */
        copyleft: function (char1, char2) {
            var artPattern = [
                [0x5c],
                [0x1e, 0x3, 0x33, 0x3, 0x5],
                [0x1e, 0x3, 0x33, 0x3, 0x5],
                [0xa, 0x5, 0x6, 0x5, 0x3, 0x3, 0x8, 0x6, 0x3, 0x2, 0x1, 0x5, 0x3, 0x3, 0x3, 0x3, 0x5, 0x5, 0x4, 0x3, 0x6],
                [0x7, 0x3, 0x2, 0x3, 0x3, 0x3, 0x2, 0x3, 0x2, 0x7, 0x3, 0x3, 0x2, 0x3, 0x3, 0x3, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x2, 0x3, 0x3, 0x3, 0x2, 0x3, 0x1, 0x4, 0x2],
                [0x5, 0x3, 0x3, 0x3, 0x2, 0x3, 0x8, 0x6, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x2, 0x3, 0x3, 0x3, 0x2, 0x5, 0x6, 0x3, 0x3, 0x3, 0x2],
                [0x4, 0x3, 0x3, 0x3, 0x2, 0x3, 0x8, 0x3, 0x6, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x2, 0x3, 0x3, 0x3, 0x3, 0x6, 0x4, 0x3, 0x3, 0x3, 0x3],
                [0x3, 0x3, 0x3, 0x3, 0x2, 0x3, 0x3, 0x3, 0x2, 0x3, 0x6, 0x3, 0x3, 0x3, 0x3, 0x3, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x5, 0x5, 0x3, 0x3, 0x3, 0x3, 0x4],
                [0x2, 0x3, 0x2, 0x3, 0x3, 0x3, 0x2, 0x3, 0x3, 0x7, 0x2, 0x3, 0x2, 0x1, 0x1, 0x2, 0x3, 0x3, 0x1, 0x3, 0x4, 0x3, 0x3, 0x3, 0x2, 0x3, 0x3, 0x3, 0x2, 0x3, 0x3, 0x3, 0x5],
                [0x2, 0x5, 0x6, 0x5, 0x6, 0x5, 0x4, 0x4, 0x2, 0x3, 0x2, 0x5, 0x7, 0x5, 0x1, 0x3, 0x3, 0x5, 0x4, 0x3, 0x4, 0x3, 0x5],
                [0x2b, 0x3, 0x2e],
                [0x2b, 0x3, 0x2e],
                [0x5c]
            ];

            var patternChars = [_o_.utility.ifNull(char1, ' '), _o_.utility.ifNull(char2, '=')];
            var str = '';

            artPattern.forEach(function (el, idx, arr) {
                var charMode = true;
                str += '\n';

                el.forEach(function (el, idx, arr) {
                    charMode = !charMode;
                    str += new Array(el + 1).join(patternChars[charMode ? 0x1 : 0x0]);
                });
            });

            return str;
        },
        /**
         * @desc Display octapush string art on browser console.
         */
        showCopyleft: function () {
            console.log(_o_.copyleft());
        },
        compare: {
            /**
             * @desc Check the object is null or not
             * @param {any} obj
             * @returns {bool} returning TRUE if object is null or '' or undefined or array with length 0. Otherwise is FALSE.
             */
            isNullOrEmpty: function (obj) {
                return _o_.compare.isUndefined(obj) || null === obj || '' === obj || 0x0 === obj.length;
            },
            /**
             * @desc Check the `obj` is having properties or not.
             * @param {object} obj
             * @returns {bool} returning TRUE if there are no properties inside `obj`. Otherwise is FALSE.
             */
            isEmpty: function (obj) {
                for (name in obj) return false;
                return true;
            },
            /**
             * @desc Check the `obj` is undefined or not.
             * @param {any} obj
             * @returns {bool} return TRUE if the `obj` is UNDEFINED. Otherwise is FALSE.
             */
            isUndefined: function (obj) {
                return obj === undefined;
            },
            /**
             * @desc Check the `obj` is defined or not.
             * @param {any} obj
             * @returns {bool} return TRUE if the `obj` is DEFINED. Otherwise is FALSE.
             */
            isDefined: function (obj) {
                return obj !== undefined;
            },
            /**
             * @desc Check the `obj` is a number or not.
             * @param {any} obj
             * @returns {bool} return FALSE if the `obj` is NUMBER. Otherwise is TRUE.
             */
            isNan: function (obj) {
                return isNaN(obj);
            },
            /**
             * @desc Check the `obj` is a number or not
             * @param {any} obj
             * @returns {bool} return TRUE if the `obj` is NUMBER. Otherwise is FALSE.
             */
            isNumber: function (obj) {
                return !isNaN(obj);
            },
            /**
             * @desc Check the `obj` is a JS function or not.
             * @param {any} obj
             * @returns {bool} return TRUE if the `obj` is a JS function. Otherwise is FALSE.
             */
            isFunction: function (obj) {
                return obj instanceof Function || _o_.utility.getType(obj) === 'function' || obj.__proto__.toString() === 'function () {\n}';
            },
            /**
             * @desc Check the `obj` is a string or not.
             * @param {any} obj
             * @returns {bool} return TRUE if the `obj` is a string. Otherwise is FALSE.
             */
            isString: function (obj) {
                return obj.__proto__ === '[object String]' || _o_.utility.getType(obj) === 'string';
            },
            /**
             * @desc Check the `obj` is a boolean or not.
             * @param {any} obj
             * @returns {bool} return TRUE if the `obj` is a boolean. Otherwise is FALSE.
             */
            isBool: function (obj) {
                return obj === true || obj === false || obj.__proto__ === '[object Boolean]' || _o_.utility.getType(obj) === 'boolean';
            },
            /**
             * @desc Check the `obj` is an array or not.
             * @param {any} obj
             * @returns {bool} return TRUE if the `obj` is an array. Otherwise is FALSE.
             */
            isArray: function (obj) {
                return _o_.compare.isUndefined(obj) ? false : Array.isArray(obj);
            },
            /**
             * @desc Check the `obj` is an integer or not.
             * @param {any} obj
             * @returns {bool} return TRUE if the `obj` is an integer. Otherwise is FALSE.
             */
            isInteger: function (obj) {
                return obj % 0x1 === 0x0;
            },
            /**
             * @desc Check the `obj` is a float or not.
             * @param {any} obj
             * @returns {bool} return TRUE if the `obj` is an float. Otherwise is FALSE.
             */
            isFloat: function (obj) {
                return Number(obj) === obj && obj % 1 !== 0;
            },
            /**
             * @desc Check the `obj` is a JSON object or not.
             * @param {any} obj
             * @returns {bool} return TRUE if the `obj` is a JSON object. Otherwise is FALSE.
             */
            isJsonObject: function (obj) {
                return _o_.compare.isUndefined(obj) ?
                    false :
                    (
                        _o_.compare.isFunction(obj) ?
                        false : {}.constructor === obj.constructor
                    )
            }
        },
        utility: {
            /**
             * @desc Getting the `obj` data type
             * @param {any} obj
             * @returns {string} Data type name;
             */
            getType: function (obj) {
                return !arguments ?
                    null :
                    (null === obj ? obj + '' : (typeof obj).toString());
            },
            /**
             * @desc Don't do anything
             */
            noop: function () {},
            /**
             * @desc Don't do anything
             */
            noOperation: function () {},
            /**
             * @desc Give the `defaultValue` if `obj` is NULL. Otherwise, give the `obj` value.
             * @param {any} obj
             * @param {any} defaultValue
             * @returns {any}  Return the `defaultValue` if `obj` is NULL. Otherwise, give the `obj` value.
             */
            ifNull: function (obj, defaultValue) {
                return _o_.compare.isNullOrEmpty(arguments) ?
                    null :
                    (!_o_.compare.isNullOrEmpty(obj) ? obj : defaultValue);
            },
            /**
             * @desc Execute `func` for `nTime`. In other word, this is simplify for JS native looping.
             * @param {Number} nTime
             * @param {Func} func
             * @returns -
             */
            loop: function (nTime, func) {
                if (!func) return;

                for (var i = 0x0; i < nTime; i++)
                    if (func && _o_.compare.isFunction(func))
                        func(i);
            },
            /**
             * @description Do some actions for each element inside `obj`
             *
             * @param {object} `obj` the object which will be iterated.
             * @param {function} `callback` the callback to be processed.
             * @param {any} args the arguments to be called by `callback`
             * @returns the original `obj`
             */
            each: function(obj, callback, args) {
                var value = null;
                var i = 0;
                var BreakException = {};

                if (callback) {
                    if (args) {
                        if (_o_.compare.isArray(obj))
                            obj.forEach(function(val, key) {
                                value = callback.apply(val, args);
                                if (false === value) throw BreakException;
                            });

                        else
                            for (i in obj) {
                                value = callback.apply(obj[i], args);
                                if (false === value) break;
                            }

                    } else {
                        if (_o_.compare.isArray(obj))
                            obj.forEach(function(val, key) {
                                value = callback.call(val, key, val);
                                if (false === value) throw BreakException;
                            });

                        else
                            for (i in obj) {
                                value = callback.call(obj[i], i, obj[i]);
                                if (false === value) break;
                            }
                    }
                }

                return obj;
            },
            /**
             * @description Do some actions for each element inside `obj`
             *
             * @param {object} `obj` the object which will be iterated.
             * @param {function} `callback` the callback to be processed.
             * @param {any} args the arguments to be called by `callback`
             * @returns the original `obj`
             */
            forEach: function (obj, callback, args) {
                _o_.utility.each(obj, callback, args)
            },
            /**
             * @description Merge the contents of two or more objects together into the first object.
             *
             * @param {object} `objects` The object to be merged.
             * @returns {object} Returning merged object.
             */
            extend: function (objects) {
                var argues = arguments;
                return _o_.compare.isNullOrEmpty(argues) ?
                    null :
                    (function () {
                        Array.prototype.slice.call(argues, 1).forEach(function (source) {
                            for (var key in source)
                                if (source[key] !== undefined)
                                    objects[key] = source[key]
                        });
                        return objects
                    })();
            }
        }
    };

    _o_.showCopyleft();
    w.octapushJS = w._o_ = _o_;
})(window);<|endoftext|>