# HG changeset patch # User Raphael Velt # Date 1318866058 -7200 # Node ID 7d9c576bfaac68dddca4ddc175cb91798277206e # Parent 77ccba188e086309c8c8e4abfa27365a6f500230 Some changes - added multi-zoom level on client diff -r 77ccba188e08 -r 7d9c576bfaac tweetcast/nodejs/client/css/style.css --- a/tweetcast/nodejs/client/css/style.css Fri Oct 14 17:36:34 2011 +0200 +++ b/tweetcast/nodejs/client/css/style.css Mon Oct 17 17:40:58 2011 +0200 @@ -27,38 +27,78 @@ /* Liste de Tweets */ -#tweetcontainer { - position: absolute; top: 25px; bottom: 5px; width: 270px; overflow: auto; -} - #tweetlist { - list-style: none; + position: absolute; left: 50px; top: 50px; width: 240px; height: 600px; list-style: none; overflow: hidden; border: 1px solid #999; } li.tweet { - border: 1px solid #999; margin: 5px; width: 240px; height: 100px; overflow: hidden; + border-width: 1px; border-style: none solid solid none; border-color: #ccc; overflow: hidden; float: left; +} + +li.tweet.full { + width: 240px; height: 99px; } -.tweet h4, .tweet p { - margin: 5px; +li.tweet.half { + width: 240px; height: 49px; +} + +li.tweet.quarter { + width: 119px; height: 49px; +} + +li.tweet.icons { + width: 29px; height: 29px; } .tweet h4 { + font-weight: bold; +} + +.full h4, .full p, .half p { + margin: 5px; +} + +.full h4 { font-size: 15px; } -.tweet p { +.full p, .half p { font-size: 12px; } +.quarter p { + font-size: 10px; margin: 2px 2px 2px 28px; +} + img.tweet_profile_image { - float: left; margin: 5px; width: 32px; height: 32px; + float: left; opacity: .7; filter:alpha(opacity=70); +} + +.full .tweet_profile_image { + margin: 5px; width: 48px; height: 48px; +} + +.half .tweet_profile_image { + margin: 5px; width: 39px; height: 39px; +} + +.quarter .tweet_profile_image { + margin: 2px; width: 25px; height: 25px; +} + + .icons .tweet_profile_image { + margin: 3px; width: 23px; height: 23px; } p.tweet_created_at { font-style: italic; color: #999; } +.half h4, .half p.tweet_created_at, .quarter h4, .quarter p.tweet_created_at, .icons h4, .icons p { + display: none +} + .a_positive { background: #c5e7cd; } diff -r 77ccba188e08 -r 7d9c576bfaac tweetcast/nodejs/client/index.html --- a/tweetcast/nodejs/client/index.html Fri Oct 14 17:36:34 2011 +0200 +++ b/tweetcast/nodejs/client/index.html Mon Oct 17 17:40:58 2011 +0200 @@ -13,10 +13,8 @@ -

Liste de Tweets

-
+

Niveau de zoom (liste de tweets) 1 - 2 - 3 - 4

-
\ No newline at end of file diff -r 77ccba188e08 -r 7d9c576bfaac tweetcast/nodejs/client/js/script.js --- a/tweetcast/nodejs/client/js/script.js Fri Oct 14 17:36:34 2011 +0200 +++ b/tweetcast/nodejs/client/js/script.js Mon Oct 17 17:40:58 2011 +0200 @@ -1,11 +1,35 @@ var socket, - tweets = [], - waitoldtweets = true, - tl, - tc; + tweetData = { + "tweetcount" : 0, + "position" : -1, + "zoomLevel" : 3, + "timeLevel" : 2 + } + zoomLevels = [ + { + "description" : "160 tweets per page", + "className" : "icons", + "displayCount" : 160 + }, + { + "description" : "24 tweets per page", + "className" : "quarter", + "displayCount" : 24 + }, + { + "description" : "12 tweets per page", + "className" : "half", + "displayCount" : 12 + }, + { + "description" : "6 tweets per page", + "className" : "full", + "displayCount" : 6 + } + ]; function tweetToHtml(tweet) { - html = '
  • '; } - html += '

    ' + a_user + '@' + tweet.user.screen_name + '

    ' + tweet.created_at + '

    '; + html += '

    ' + a_user + '@' + tweet.user.screen_name + '

    ' + new Date(tweet.created_at).toLocaleTimeString() + '

    '; lastend = 0; txt = ''; entities = []; @@ -49,105 +73,33 @@ return html; } -function discardTweets() { - if (tweets.length) { - while (tl.height() - (tc.scrollTop() + tc.height()) > 1000) { - tl.children().last().detach(); - tweets.pop(); - } - } +function getTweets() { + var to = tweetData.position + 1, + from = Math.max(0, to - zoomLevels[tweetData.zoomLevel].displayCount); + socket.emit('getTweets',{ "from": from, "to": to }); +} + +function setZoom(level) { + tweetData.zoomLevel = Math.max(0, Math.min( zoomLevels.length - 1 , level ) ); + getTweets(); } -// function scheduleTweets() { -// var tl = $("#tweetlist"), tc = $("#tweetcontainer"); -// if (tweets.length != tl.children().length) { -// console.log("Tweet count error"); -// } -// if (tweets.length) { -// while (tl.height() - (tc.scrollTop() + tc.height()) > 1000) { -// tl.children().last().detach(); -// tweets.pop(); -// } -// if (tl.height() - (tc.scrollTop() + tc.height()) < 120) { -// getTweets({ -// before_id : tweets[tweets.length - 1].id, -// limit : 5 -// }); -// } -// getTweets({ -// after_id : tweets[0].id -// }); -// } else { -// getTweets({ -// limit: 15 -// }); -// } -// } -// -// function getTweets(params) { -// $.getJSON("http://" + document.location.hostname + ":8888/?callback=?", params, function(data, a, b) { -// if (data.tweets && data.tweets.length) { -// var tl = $("#tweetlist"), tc = $("#tweetcontainer"); -// html = ''; -// for (var i in data.tweets) { -// html += tweetToHtml(data.tweets[i]); -// } -// if (params.before_id) { -// tl.append(html); -// tweets = tweets.concat(data.tweets); -// } else { -// var pos = tc.scrollTop(), fixScroll = (pos > 80); -// pos -= tl.height(); -// tl.prepend(html); -// tweets = data.tweets.concat(tweets); -// if (fixScroll) { -// tc.scrollTop(tl.height() + pos); -// } -// } -// } -// }); -// } - $(document).ready(function() { - tl = $("#tweetlist"); - tc = $("#tweetcontainer"); socket = io.connect('http://' + S_IO_HOST + ':' + S_IO_PORT ); + socket.on('tweetSummary', function (data) { + if (tweetData.position == tweetData.tweetcount - 1) { + tweetData.position = data.tweetcount - 1; + getTweets(); + } + tweetData.tweetcount = data.tweetcount; + }); socket.on('tweets', function (data) { - tweets = data; + tweetData.tweetsOnDisplay = data; data.reverse(); html = ''; for (var i in data) { html += tweetToHtml(data[i]); } $("#tweetlist").html(html); - discardTweets(); - }); - socket.on('oldtweets', function (data) { - tweets = tweets.concat(data); - html = ''; - for (var i = data.length - 1; i >= 0; i--) { - html += tweetToHtml(data[i]); - } - $("#tweetlist").append(html); - discardTweets(); - waitoldtweets = true; - }); - socket.on('newtweet', function (data) { - tweets.splice(0,0,data); - html = tweetToHtml(data); - var scrollpos = tc.scrollTop(), - fixScroll = (scrollpos > 80); - scrollpos -= tl.height(); - $("#tweetlist").prepend(html); - if (fixScroll) { - tc.scrollTop(tl.height() + scrollpos); - } - discardTweets(); - }); - $("#tweetcontainer").scroll(function() { - if ( waitoldtweets && tl.height() - (tc.scrollTop() + tc.height()) < 120 ) { - socket.emit('tweetsbefore', tweets[tweets.length-1].id ); - waitoldtweets = false; - } }); }); \ No newline at end of file diff -r 77ccba188e08 -r 7d9c576bfaac tweetcast/nodejs/server/node-direct.js --- a/tweetcast/nodejs/server/node-direct.js Fri Oct 14 17:36:34 2011 +0200 +++ b/tweetcast/nodejs/server/node-direct.js Mon Oct 17 17:40:58 2011 +0200 @@ -1,10 +1,21 @@ +READ_OLD_TWEETS = true; +RECORD_NEW_TWEETS = true; +TWEET_FILE_PATH = 'tweets.txt'; +TRACKING_KEYWORD = 'Bieber'; + var fs = require('fs'), https = require('https'), io = require('socket.io') - .listen(8000) - keyword = "Bieber", + .listen(8000), tweets = [], tweet_ids = [], + date_struct = [], + date_levels = [ + 3600 * 1000, + 15 * 60 * 1000, + 5 * 60 * 1000, + 60 * 1000 + ], annkw = { 'positive' : '++', 'negative' : '--', @@ -12,6 +23,67 @@ 'question' : '??' } +function populateDateStruct(level, start) { + var end = start + date_levels[level], + struct = { + "level" : level, + "start" : new Date(start), + "end" : new Date(end) + }; + if (level < date_levels.length - 1) { + struct.slices = []; + var newstart = start; + while (newstart < end) { + struct.slices.push(populateDateStruct(level + 1, newstart)); + newstart += date_levels[level + 1]; + } + } else { + struct.tweets = []; + struct.annotations = []; + } + return struct; +} + +function insertIntoDateStruct(slices, tweet) { + var creadate = new Date(tweet.created_at); + for (var i in slices) { + if (creadate < slices[i].end) { + if (slices[i].slices) { + insertIntoDateStruct(slices[i].slices, tweet); + } else { + slices[i].tweets.push(tweet.pos); + for (var j in tweet.annotations) { + if (slices[i].annotations[j]) { + slices[i].annotations[j]++; + } else { + slices[i].annotations[j] = 1; + } + break; + } + } + break; + } + } +} + +function addToList(tweet) { + if (tweet_ids.indexOf(tweet.id) != -1) { + console.log("Error: Tweet already in list"); + return; + } + tweet.pos = tweets.length; + tweets.push(tweet); + tweet_ids.push(tweet.id); + var creadate = new Date(tweet.created_at); + if (!date_struct.length) { + date_struct = [ populateDateStruct(0, date_levels[0] * parseInt(creadate / date_levels[0])) ] + } + while (creadate > date_struct[date_struct.length - 1].end) { + date_struct.push( populateDateStruct(0, date_struct[date_struct.length - 1].end) ); + } + insertIntoDateStruct(date_struct, tweet); +} + function textids(object) { for (var key in object) { if (key.substr(-2) == 'id') { @@ -21,54 +93,84 @@ } } -var fd = fs.createWriteStream('tweets.txt'); - -req = https.request({ - host: "stream.twitter.com", - path: "/1/statuses/filter.json", - method: "POST", - headers: { - 'Authorization': 'Basic cmFwaHY6N3czMzdMZkMyM2dF', - 'Content-Type': 'application/x-www-form-urlencoded' - } -}, function(res) { - console.log('STATUS: ' + res.statusCode); - console.log('HEADERS: ' + JSON.stringify(res.headers)); - res.setEncoding('utf8'); - res.on('data', function(chunk) { - newdata = chunk.split('\r\n'); - try { - for (var i in newdata) { - if (newdata[i].length > 0) { - tweet = JSON.parse(newdata[i]); - annotations = []; - for (var a in annkw) { - if (tweet.text.indexOf(annkw[a]) != -1) { - annotations.push(a); - } - } - tweet.annotations = annotations; - tweets.push(tweet); - textids(tweet); - tweet_ids.push(tweet.id_str); - io.sockets.emit('newtweet', tweet); +if (READ_OLD_TWEETS) { + try { + var filebuff = ""; + readstream = fs.createReadStream(TWEET_FILE_PATH, { flags: 'r', encoding: 'utf-8' }); + readstream.on("data", function(data) { + console.log("data"); + filebuff += data; + }); + readstream.on("end", function() { + console.log("end"); + oldtweets = filebuff.split('\r\n'); + var tweetscopied = 0; + for (var i in oldtweets) { + if (oldtweets[i].length > 0) { + addToList(JSON.parse(oldtweets[i])); + tweetscopied++; } } - fd.write(chunk); - } - catch(err) { - console.log(err); + console.log(tweetscopied, "tweets copied from", TWEET_FILE_PATH); + }); + } catch(err) { + console.log(err); + } +} + +if (RECORD_NEW_TWEETS) { + var writestream = null; + var req = https.request({ + host: "stream.twitter.com", + path: "/1/statuses/filter.json", + method: "POST", + headers: { + 'Authorization': 'Basic cmFwaHY6N3czMzdMZkMyM2dF', + 'Content-Type': 'application/x-www-form-urlencoded' } + }, function(res) { + console.log('STATUS: ' + res.statusCode); + console.log('HEADERS: ' + JSON.stringify(res.headers)); + res.setEncoding('utf8'); + res.on('data', function(chunk) { + var newdata = chunk.split('\r\n'); + try { + for (var i in newdata) { + if (newdata[i].length > 0) { + var tweet = JSON.parse(newdata[i]), + annotations = []; + for (var a in annkw) { + if (tweet.text.indexOf(annkw[a]) != -1) { + annotations.push(a); + } + } + tweet.annotations = annotations; + textids(tweet); + addToList(tweet); + } + } + if (!writestream) { + writestream = fs.createWriteStream(TWEET_FILE_PATH, { flags: 'a', encoding: 'utf-8' }); + } + writestream.write(chunk); + io.sockets.emit('tweetSummary', { tweetcount : tweets.length }); + console.log("New tweets received. We now have", tweets.length, "tweets in memory"); + } + catch(err) { + console.log(err); + } + }); }); -}); + + req.write('track=' + encodeURIComponent(TRACKING_KEYWORD)); + req.end(); +} -req.write('track=' + encodeURIComponent(keyword)); -req.end(); io.set('log level', 0); io.sockets.on('connection', function(socket) { - socket.emit('tweets', tweets.slice(-10)); - socket.on('tweetsbefore', function(data) { - tweetpos = tweet_ids.indexOf(data); - socket.emit('oldtweets', tweets.slice(0, tweetpos).slice(-10)); + console.log("New connection", socket); + socket.emit('tweetSummary', { tweetcount : tweets.length }); + socket.on('getTweets', function(data) { + socket.emit('tweets', tweets.slice(Math.max(0, data.from), data.to)); }); }); \ No newline at end of file