summaryrefslogblamecommitdiff
path: root/public/js/mv/connect.js
blob: a17febb35a4be139755b1e43dac3fc103bbc2756 (plain) (tree)
1
2
3
4
5
6
             
                       



                     











                                                                                  
                          








                                                                 
                                                           

                                                             




                                   



                                                                                       






















                                                     







                                      

                                        



                                                 









                                                                                                                   




                                                

                                                                                               

                                  










                                                                                                           


                                                                                               



                                                                             



                                       
                                                                                                       


                                             

                   

                                                                     



                                          
                               
















                                                                             









                                   

                                                                     
















                                                                             
























                                                                                  

                











                                                                       






                                           
                                   

             



                                                     



                                                                                   
           
                





                                           
           
         









                                                     




                                                                          





















                                                                                         

        
             




                      
"use strict";
var mv = function(mv) {
  mv.connect = {
    connect: connect,
    join: join,
    part: part
  };
  /* If we're updating due to something we received, then don't broadcast back. */
  var netrendering = false;
  /* Our ID */
  var id = 0;
  /* List of all users */
  var users = {};
  /* The user status box */
  var usersStatus = d3.select("#users-status");
  /* io.socket's socket */
  var socket;
  function connect() {
    socket = io.connect();
    /* These are still useful to troubleshoot */
    socket.on("connect", function() {
      console.log("CONNECT", arguments);
    });
    socket.on("disconnect", function() {
      console.log("DISCONNECT", arguments);
      d3.select("#connection-warning").style("display", "block");
    });
    /* We're evidently operating online, so show the status */
    d3.select("#connect-status").style("display", "block");
    /* Tell the server our starting filters */
    socket.emit("filter", { filters: mv.charter.filters() });
    /*
     * Protocol:
     *  selflogin -> id (I)
     *  login -> id (I), nick (S)
     *  nickset -> id (I), nick (S)
     *  join -> id (I), channel (I)
     *  part -> id (I)
     *  filterset -> id (I), filters ({dim (S) -> filter (*)})
     *  users -> { id -> { nick (S), channel (I), filters ({dim (S) -> filter (*)}) } }
     *  logout -> id (I)
     */
    socket.on('selflogin', function(d) {
      /* Acknowledged that we logged in */
      /* Take note of our ID. */
      id = d.id;
    });
    socket.on('login', function(d) {
      /* Someone else logging in */
      users[d.id] = { nick: d.nick, filters: {} };
      updateUsersStatus();
    });
    socket.on('users', function(d) {
      /* We've got a list of all users. */
      /* The server is always right. */
      users = d.users;
      updateUsersStatus();
    });
    socket.on('nickset', function(d) {
      /* Someone, possibly us, changed their nick. */
      users[d.id].nick = d.nick;
      updateUsersStatus();
    });
    socket.on('join', function(d) {
      users[d.id].channel = d.channel;
      updateUsersStatus();
    });
    socket.on('part', function(d) {
      delete users[d.id].channel;
      updateUsersStatus();
    });
    socket.on('filterset', function(d) {
      /* Someone changed their filter */
      /* Ignore server sourced (ID: 0) changes */
      if (d.id) {
        users[d.id].filters = d.filters;
      }
      /* Use the variable netrendering to denote that we're rendering due to a change received from the network. */
      netrendering = true;
      setOwnFilters(d.filters);
      netrendering = false;
    });
    socket.on('logout', function(d) {
      /* Someone disconnected, take them off the list. */
      delete users[d.id];
      updateUsersStatus();
    });
    /*
     * Remember the old renderlet
     * FIXME: Find a more elegant way to do this
     */
    var f = dc.renderlet();
    dc.renderlet(function() {
      /* Hook a listener into dc's rendering routine. If it rerenders, broadcast the change. */
      /* Call the old renderlet */
      f();
      if (netrendering) {
        /* If we rendered due a change we received, don't broadcast it again. That would be A Bad Thing. */
        return;
      }
      socket.emit("filter", { filters: mv.charter.filters() });
    });
  }
  function setOwnFilters(filters) {
    /* See if there's any difference - if there isn't, don't update. */
    var change = false;
    var key;
    function filterCompare(key, l, r) {
      return ("filterCompare" in mv.charts[key]) ? mv.charts[key].filterCompare(l, r) : l == r;
    }
    /* Check for keys in the filters to apply which are not in our charts. */
    for (key in filters) {
      if (!(key in mv.charts))
        continue;
      /* The two filters to compare. */
      var l = mv.charts[key].filter();
      var r = filters[key];
      if (l instanceof Array) {
        /* Crossfilter uses arrays to filter ranges. Exactly the first two elements are significant. */
        if ((r instanceof Array)
         && filterCompare(key, l[0], r[0])
         && filterCompare(key, l[1], r[1])) {
          continue;
        }
      } else if (!(r instanceof Array) && filterCompare(key, l, r)) {
        /* Exact filter */
        continue;
      }
      /* This filter differs. Apply it. */
      change = true;
      mv.charts[key].filter(r);
    }
    /* Check for keys in our charts which are not in the filters to apply. */
    for (key in mv.charts) {
      if (mv.charts[key].filter() != null) {
        if (key in filters) {
          /* This has already been handled above */
          continue;
        }
        /* There is no longer a filter applying here, clear it. */
        change = true;
        mv.charts[key].filterAll();
      }
    }
    if (change) {
      dc.redrawAll();
    }
  }
  function join(channel) {
    if (channel != null) {
      socket.emit("join", channel);
    } else {
      socket.emit("join");
    }
  }
  function part() {
    socket.emit("part");
  }
  function updateUsersStatus() {
    /* Convert the user list to a form suitable for a d3 selection */
    var groups = [];
    /* Track the groupless people separately. They should come at the end. */
    var unchannelled = [];
    var channelById = {};
    var channelNames = [];
    for (var uid in users) {
      users[uid].id = uid;
      if ("channel" in users[uid]) {
        if (!(users[uid].channel in channelById)) {
          groups.push(channelById[users[uid].channel] = []);
          channelNames.push(users[uid].channel);
        }
        channelById[users[uid].channel].push(users[uid]);
      } else {
        unchannelled.push(users[uid]);
      }
    }
    groups.push(unchannelled);
        var createpart = usersStatus.select(".createpart");
    /* Link to part a channel we're in, or create a channel if we're not in one */
    if (createpart.empty()) {
      createpart = usersStatus.append("a")
        .attr("class", "createpart")
      ;
    }
    if ("channel" in users[id]) {
      createpart
        .attr("href", "javascript:mv.connect.part();")
        .text("Part channel")
      ;
    } else {
      createpart
        .attr("href", "javascript:mv.connect.join();")
        .text("Create channel")
      ;
    }
    /* List of groups, with unchanneled users at the end */
    var grouplist = usersStatus.selectAll(".group")
      .data(groups, function(d, i) { return channelNames[i]; })
    ;
    grouplist
      .enter().append("div").attr("class", "group")
    ;
    /* Update */
    grouplist
      .each(function(d, i) {
        var group = d3.select(this);
        var ul = group.select("ul");
        if (ul.empty()) {
          ul = group.append("ul");
        }
        /*
         * Hacky way to check if these are the users without a channel.
         * Feel free to FIXME!
         */
        if (i != groups.length - 1) {
          group
            .attr("class", "group channel")
          ;
          var join = group.select(".join");
          if (join.empty()) {
            join = group.append("a")
              .attr("class", "join")
              .text("Join channel")
            ;
          }
          if (users[id].channel == channelNames[i]) {
            /* We're in this channel */
            join.style("display", "none");
          } else {
            join
              .style("display", "inline")
              .attr("href", "javascript:mv.connect.join(" + channelNames[i] + ");")
            ;
          }
        } else {
          var join = group.select(".join");
          group
            .attr("class", "group")
          ;
          if (join.empty()) {
            join.remove();
          }
        }
        /* Update the list of users in this group */
        var userlist = ul.selectAll(".user")
          .data(d, function(d) { return d.id; })
        ;
        userlist
          .enter().append("li").attr("class", "user")
        ;
        userlist
          .each(function(d,i) {
            var elm = d3.select(this);
            var name = elm.select(".name");
            var nick = d.nick == null ? "Anonymous User " + d.id : d.nick;
            if (d.id == id) {
              /* This is us! We can edit our name. */
              if (name.empty()) {
                name = elm.append("input").attr("class", "name")
                  .attr("type", "text")
                  .attr("placeholder", "Enter name here")
                  .on("change", function () {
                    /* A d3 select must be dereferenced twice to access the DOM entity */
                    socket.emit("nick", { nick: name[0][0].value });
                  })
                ;
              }
              name.attr("value", nick);
            } else {
              /* This is someone else. We can't edit their name. */
              if (name.empty()) {
                name = elm.append("p").attr("class", "name");
              }
              name.text(nick);
            }
          })
        ;
        userlist
          .exit().remove()
        ;
      })
    ;
    grouplist
      .exit().remove()
    ;
  }
  return mv;
}(mv || {});