From 4e83d0b4c7e2b303689ac1b8df6373a987ab1944 Mon Sep 17 00:00:00 2001 From: Freeyorp Date: Tue, 14 May 2013 21:08:11 +1200 Subject: Add channels Active filters will only be applied from users in the same channel. This closes #14. --- index.js | 74 ++++++++++++++++++- public/css/style.css | 13 +++- public/index.html | 2 +- public/js/mv/connect.js | 193 +++++++++++++++++++++++++++++++++++++++--------- public/js/mv/main.js | 2 +- public/js/mv/parse.js | 3 +- 6 files changed, 243 insertions(+), 44 deletions(-) diff --git a/index.js b/index.js index 5a2c7c2..8116dfb 100644 --- a/index.js +++ b/index.js @@ -33,12 +33,16 @@ logger.format = function(level, date, message) { /* Number of sessions we've seen. */ var count = 0; -/* nid -> { nick, filters, following } */ +/* nid -> { nick, filters, channel } */ var users = {}; /* nid -> socket */ var sockets = {}; /* FIXME: Workaround to prevent logout propagating during ghosting */ var ghosting = false; +/* Auto-incrementing room count */ +var channelCount = 0; +/* Channel ID -> { usernum, filters } */ +var channels = {}; sessionSockets.on('connection', function (err, socket, session) { /* @@ -86,13 +90,73 @@ sessionSockets.on('connection', function (err, socket, session) { nick: d.nick }); }); + socket.on('join', function(d) { + var channel; + if (d != null) { + if (!(typeof(d) == "number" && d <= channelCount)) { + return; + } + /* Join an existing channel */ + channel = d; + } else { + /* Automagically create a new channel */ + channel = ++channelCount; + } + logAction("JOIN", users[session.nid].channel); + if ("channel" in users[session.nid]) { + /* Leave any channel we're in */ + socket.leave(users[session.nid].channel); + } + /* Inform socket.io about the channel join */ + socket.join(channel); + /* Let everyone know about the channel join */ + users[session.nid].channel = channel; + io.sockets.emit('join', { + id: session.nid, + channel: channel + }); + /* Update the channel information */ + if (channel in channels) { + /* This channel already exists. Inform the joining user of the current filters. */ + socket.emit('filterset', { + id: 0, /* Server */ + filters: channels[channel].filters + }); + ++channels[channel].usernum; + } else { + /* This channel didn't already exist, so create it and set the filters. */ + channels[channel] = { + usernum: 1, + filters: users[session.nid].filters + }; + } + }); + socket.on('part', function() { + if (!users[session.nid].channel) { + return; + } + logAction("PART"); + socket.leave(users[session.nid].channel); + if (!--channels[users[session.nid].channel].usernum) { + delete channels[users[session.nid].channel]; + } + delete users[session.nid].channel; + io.sockets.emit('part', { + id: session.nid + }); + }); socket.on('filter', function(d) { if (!(typeof(d) == "object" && "filters" in d)) { return; } users[session.nid].filters = d.filters; logAction("FILTER", d.filters); - socket.broadcast.emit('filterset', { + var channel = users[session.nid].channel; + if (!channel) { + return; + } + channels[channel].filters = d.filters; + socket.broadcast.to(channel).emit('filterset', { id: session.nid, filters: d.filters }); @@ -111,7 +175,11 @@ sessionSockets.on('connection', function (err, socket, session) { }); }); function logAction(action, content) { - logger.info(session.nid, action, content); + if (arguments.length > 1) { + logger.info(session.nid, action, content); + } else { + logger.info(session.nid, action); + } } }); diff --git a/public/css/style.css b/public/css/style.css index d223226..0e1e244 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -151,9 +151,20 @@ h3 { border-left: 1px black solid; border-bottom: 1px black solid; display: none; + height: 200px; + overflow-y: scroll; } -#users-status { +#users-status .channel { + border: 1px gray solid; + margin: 0 2em; +} + +#users-status a { + margin-left: 1em; +} + +#users-status ul { padding-left: 20px; } diff --git a/public/index.html b/public/index.html index 0ab91a1..a016fb1 100644 --- a/public/index.html +++ b/public/index.html @@ -9,7 +9,7 @@

Manavis users

-
    +

    Character base level

    diff --git a/public/js/mv/connect.js b/public/js/mv/connect.js index 0fee5e7..564690d 100644 --- a/public/js/mv/connect.js +++ b/public/js/mv/connect.js @@ -1,7 +1,9 @@ "use strict"; var mv = function(mv) { - mv.socket = { - connect: connect + 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; @@ -19,12 +21,17 @@ var mv = function(mv) { socket.on("disconnect", function() { console.log("DISCONNECT", arguments); }); d3.select("#connect-status").style("display", "block"); socket.emit('login'); + /* 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) - * users -> { id -> {nick (S), filters ({dim (S) -> filter (*)}) } } + * 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) { @@ -48,9 +55,20 @@ var mv = function(mv) { 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 */ - users[d.id].filters = d.filters; + /* 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); @@ -83,6 +101,13 @@ var mv = function(mv) { var key; /* Check for keys in the filters to apply which are not in our charts. */ for (key in filters) { + if (key == "date") { + /* + * Special case! FIXME: Find a more elegant way to handle this + */ + filters[key][0] = new Date(filters[key][0]); + filters[key][1] = new Date(filters[key][1]); + } if (!(key in mv.charts)) continue; var filter = mv.charts[key].filter(); @@ -115,49 +140,145 @@ var mv = function(mv) { 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 data = []; - for (var uid in users) { users[uid].id = uid; data.push(users[uid]); } - /* Data join */ - var userlist = usersStatus.selectAll(".user") - .data(data, function(d) { return d.id; }); - /* Enter */ - userlist - .enter().append("li").attr("class", "user") + 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]); + } + } + console.log("Channels:", groups, "Users without channels:", unchannelled); + 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 */ - userlist - .each(function(d,i) { - var elm = d3.select(this); - console.log("Userlist para appending", d,i); - 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()) { - console.log("Found our entry. id:", id, "datum", d); - 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 }); - }) + 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) { + console.log("Group is a channel", i, groups.length); + group + .attr("class", "group channel") + ; + var join = group.select(".join"); + if (join.empty()) { + join = group.append("a") + .attr("class", "join") + .attr("href", "javascript:mv.connect.join(" + channelNames[i] + ");") + .text("Join channel") ; } - name.attr("value", nick); + if (users[id].channel == channelNames[i]) { + /* We're in this channel */ + join.style("display", "none"); + } else { + join.style("display", "inline"); + } } else { - /* This is someone else. We can't edit their name. */ - if (name.empty()) { - name = elm.append("p").attr("class", "name"); + console.log("Group is not a channel", i, groups.length); + var join = group.select(".join"); + group + .attr("class", "group") + ; + if (join.empty()) { + join.remove(); } - name.text(nick); } + /* 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); + console.log("Userlist para appending", d,i); + 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()) { + console.log("Found our entry. id:", id, "datum", d); + 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() + ; }) ; - /* Remove */ - userlist + grouplist .exit().remove() ; } diff --git a/public/js/mv/main.js b/public/js/mv/main.js index eb802e0..d7ebdf9 100644 --- a/public/js/mv/main.js +++ b/public/js/mv/main.js @@ -58,7 +58,7 @@ var mv = function(mv) { mv.charter.init(); console.log(document.getElementById("connect-option").checked); if (document.getElementById("connect-option").checked) { - mv.socket.connect(); + mv.connect.connect(); } }, 2000); } diff --git a/public/js/mv/parse.js b/public/js/mv/parse.js index b30a19a..2c8b916 100644 --- a/public/js/mv/parse.js +++ b/public/js/mv/parse.js @@ -218,7 +218,6 @@ var mv = function(mv) { } function parseScrubbed(scrubbedRecords) { scrubbedRecords = JSON.parse(scrubbedRecords); - console.log(scrubbedRecords, scrubbedRecords.length); /* * The work is mostly all done for us. Just scan through to see if there * are any undefined records, and update the pointer if so. @@ -236,7 +235,7 @@ var mv = function(mv) { } } /* It's simple when everything's already been done. */ - parser.records = parser.records.concat(scrubbedRecords); + mv.parser.records = mv.parser.records.concat(scrubbedRecords); } return mv; }(mv || {}); -- cgit v1.2.3-60-g2f50