├── web-server
├── static
│ ├── images
│ │ ├── lines.png
│ │ ├── njs.png
│ │ ├── chruch.png
│ │ ├── favicon.png
│ │ ├── project_papper.png
│ │ ├── subtle_zebra_3d.png
│ │ └── html5-badge-h-connectivity-css3-graphics-multimedia-performance.png
│ ├── impress
│ │ ├── apple-touch-icon.png
│ │ ├── README.md
│ │ ├── js
│ │ │ └── impress.js
│ │ ├── css
│ │ │ └── impress-demo.css
│ │ └── index.html
│ ├── index.html
│ ├── basicemitter.js
│ ├── init.js
│ ├── reset.css
│ ├── basics.js
│ ├── overlay.js
│ ├── interface.js
│ ├── emitter.js
│ └── style.css
├── express.js
└── views
│ ├── index.ejs
│ └── pres.ejs
├── api-client
├── apiConf.js
└── restreamer.js
├── configs
├── nginx
│ ├── conf.d
│ │ ├── vhosts.conf
│ │ ├── example_ssl.conf
│ │ └── default.conf
│ └── nginx.conf
├── init.d
│ ├── nodeforeverexpress
│ └── nodeforeverrestreamer
└── haproxy
│ └── haproxy.cfg
├── lib
├── parser.js
├── Client.js
├── RestreamServer.js
└── Streamer.js
└── README.md
/web-server/static/images/lines.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/defly/tweereal/HEAD/web-server/static/images/lines.png
--------------------------------------------------------------------------------
/web-server/static/images/njs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/defly/tweereal/HEAD/web-server/static/images/njs.png
--------------------------------------------------------------------------------
/web-server/static/images/chruch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/defly/tweereal/HEAD/web-server/static/images/chruch.png
--------------------------------------------------------------------------------
/web-server/static/images/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/defly/tweereal/HEAD/web-server/static/images/favicon.png
--------------------------------------------------------------------------------
/web-server/static/images/project_papper.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/defly/tweereal/HEAD/web-server/static/images/project_papper.png
--------------------------------------------------------------------------------
/web-server/static/images/subtle_zebra_3d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/defly/tweereal/HEAD/web-server/static/images/subtle_zebra_3d.png
--------------------------------------------------------------------------------
/web-server/static/impress/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/defly/tweereal/HEAD/web-server/static/impress/apple-touch-icon.png
--------------------------------------------------------------------------------
/api-client/apiConf.js:
--------------------------------------------------------------------------------
1 | var authConf = {
2 | "consumerKey":"",
3 | "consumerSecret":"",
4 | "tokenKey":"",
5 | "tokenSecret":""
6 | }
7 |
8 | exports.authConf = authConf;
--------------------------------------------------------------------------------
/configs/nginx/conf.d/vhosts.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen *:8000;
3 | server_name tweereal.com www.tweereal.com;
4 | access_log /var/apps/real/clean.log main;
5 | root /var/apps/real;
6 | }
7 |
--------------------------------------------------------------------------------
/web-server/static/images/html5-badge-h-connectivity-css3-graphics-multimedia-performance.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/defly/tweereal/HEAD/web-server/static/images/html5-badge-h-connectivity-css3-graphics-multimedia-performance.png
--------------------------------------------------------------------------------
/lib/parser.js:
--------------------------------------------------------------------------------
1 | var parser = function(chunk, buffer, handler) {
2 | var index, json;
3 | var delimiter = "\r\n";
4 | buffer += chunk.toString("utf8");
5 | while ((index = buffer.indexOf(delimiter)) !== -1) {
6 | json = buffer.slice(0, index);
7 | buffer = buffer.slice(index + delimiter.length);
8 | if (json.length > 0) handler(json);
9 | }
10 | }
11 |
12 | exports.parser = parser;
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Tweereal
2 | ---------------
3 |
4 | Real-time twitter map based on Twitter Streaming API. Builded on node.js, websockets and canvas.
5 |
6 | ### Dependencies ###
7 |
8 | + socket.io
9 | + oauth-client
10 | + express
11 |
12 | ### Backend ###
13 |
14 | + HAProxy as front-end server
15 | + nginx for serving static content
16 | + forever (nodejitsu) for making fault tolerant application
17 | + init.d scripts
18 |
19 | ### Executables ###
20 | + api-client/restreamer.js - client for twitter api
21 | + web-server/express.js - webserver
--------------------------------------------------------------------------------
/configs/nginx/conf.d/example_ssl.conf:
--------------------------------------------------------------------------------
1 | # HTTPS server
2 | #
3 | #server {
4 | # listen 443;
5 | # server_name localhost;
6 |
7 | # ssl on;
8 | # ssl_certificate /etc/nginx/cert.pem;
9 | # ssl_certificate_key /etc/nginx/cert.key;
10 |
11 | # ssl_session_timeout 5m;
12 |
13 | # ssl_protocols SSLv2 SSLv3 TLSv1;
14 | # ssl_ciphers HIGH:!aNULL:!MD5;
15 | # ssl_prefer_server_ciphers on;
16 |
17 | # location / {
18 | # root /usr/share/nginx/html;
19 | # index index.html index.htm;
20 | # }
21 | #}
22 |
--------------------------------------------------------------------------------
/web-server/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/configs/nginx/nginx.conf:
--------------------------------------------------------------------------------
1 |
2 | user nginx;
3 | worker_processes 1;
4 |
5 | error_log /var/log/nginx/error.log warn;
6 | pid /var/run/nginx.pid;
7 |
8 |
9 | events {
10 | worker_connections 1024;
11 | }
12 |
13 |
14 | http {
15 | include /etc/nginx/mime.types;
16 | default_type application/octet-stream;
17 |
18 | log_format main '$remote_addr - $remote_user [$time_local] "$request" '
19 | '$status $body_bytes_sent "$http_referer" '
20 | '"$http_user_agent" "$http_x_forwarded_for"';
21 |
22 | access_log /var/log/nginx/access.log main;
23 |
24 | sendfile on;
25 | #tcp_nopush on;
26 |
27 | keepalive_timeout 65;
28 |
29 | #gzip on;
30 |
31 | include /etc/nginx/conf.d/*.conf;
32 | }
33 |
--------------------------------------------------------------------------------
/configs/init.d/nodeforeverexpress:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | DIR=/var/apps/real
3 | NODE=/usr/bin/node
4 | FOREVER=/usr/bin/forever
5 | NAME=express
6 | USER=defly
7 |
8 | test -x $NODE || exit 0
9 |
10 | function start_app {
11 | sudo -u $USER $FOREVER stop $DIR/$NAME.js
12 | # NODE_ENV=production $FOREVER start -a -l $DIR/logs/$NAME.log -o $DIR/logs/$NAME.output.log -e $DIR/logs/$NAME.error.log $DIR/$NAME.js
13 | sudo -u $USER NODE_ENV=production $FOREVER start -a -l $DIR/logs/$NAME.log $DIR/$NAME.js
14 | }
15 |
16 | function stop_app {
17 | sudo -u $USER $FOREVER stop $DIR/$NAME.js
18 | }
19 |
20 | function restart_app {
21 | sudo -u $USER $FOREVER restart $DIR/$NAME.js
22 | }
23 |
24 | case $1 in
25 | start)
26 | start_app ;;
27 | stop)
28 | stop_app ;;
29 | restart)
30 | restart_app
31 | ;;
32 | *)
33 | echo "usage: nodeforever$NAME {start|stop|restart}" ;;
34 | esac
35 | exit 0
36 |
--------------------------------------------------------------------------------
/configs/init.d/nodeforeverrestreamer:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | DIR=/var/apps/real
3 | NODE=/usr/bin/node
4 | FOREVER=/usr/bin/forever
5 | NAME=restreamer
6 | USER=defly
7 |
8 | test -x $NODE || exit 0
9 |
10 | function start_app {
11 | sudo -u $USER $FOREVER stop $DIR/$NAME.js
12 | # NODE_ENV=production $FOREVER start -a -l $DIR/logs/$NAME.log -o /dev/null -e $DIR/logs/$NAME.error.log --sourceDir $DIR $DIR/$NAME.js
13 | sudo -u $USER NODE_ENV=production $FOREVER start -a -l $DIR/logs/$NAME.log $DIR/$NAME.js
14 | }
15 |
16 | function stop_app {
17 | sudo -u $USER $FOREVER stop $DIR/$NAME.js
18 | }
19 |
20 | function restart_app {
21 | sudo -u $USER $FOREVER restart $DIR/$NAME.js
22 | }
23 |
24 | case $1 in
25 | start)
26 | start_app ;;
27 | stop)
28 | stop_app ;;
29 | restart)
30 | restart_app
31 | ;;
32 | *)
33 | echo "usage: nodeforever$NAME {start|stop|restart}" ;;
34 | esac
35 | exit 0
36 |
--------------------------------------------------------------------------------
/web-server/static/basicemitter.js:
--------------------------------------------------------------------------------
1 | Activity.BasicEmitter = function(div, options) {
2 | this.projection = null;
3 | this.div = div;
4 | // this.allowed = false;
5 | // this.map = map;
6 | this.options = this.options || {size:10, time:600};
7 |
8 | // this.updateBounds = function() {
9 | // var mapBounds = this.map.getBounds();
10 | // var sw = mapBounds.getSouthWest();
11 | // var ne = mapBounds.getNorthEast();
12 | // this.bounds = {
13 | // top: ne.lat(),
14 | // right: ne.lng(),
15 | // bottom: sw.lat(),
16 | // left: sw.lng()
17 | // }
18 | // }
19 | this.setProjection = function(projection) {
20 | this.projection = projection;
21 | }
22 | this.setCurrentOptions = function(opt) {
23 | this.options = opt;
24 | }
25 | this.start = function() {
26 | this.allowed = true;
27 | }
28 | this.stop = function() {
29 | this.allowed = false;
30 | }
31 | this.clear = function() {
32 |
33 | }
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/lib/Client.js:
--------------------------------------------------------------------------------
1 | var Socket = require('net').Socket;
2 | var EventEmitter = require('events').EventEmitter;
3 | var parser = require("./parser.js").parser;
4 | var util = require('util');
5 |
6 | var Client = function(port, host) {
7 | var self = this;
8 | var buffer = "";
9 | var socket = new Socket();
10 | var strHandler = function(str) {
11 | self.emit("str", str);
12 | }
13 |
14 | self.setMaxListeners(0);
15 |
16 | socket.setEncoding('utf8');
17 | socket.connect(port, host);
18 |
19 | socket.on("data", function(chunk) {
20 | parser(chunk, buffer, strHandler);
21 | });
22 |
23 | socket.on("error", function(error) {
24 | console.log("Error", error);
25 | });
26 |
27 | socket.on("close", function() {
28 | console.log("Reconnect:", (new Date()).toUTCString());
29 | setTimeout(function() {
30 | socket.connect(port, host);
31 | }, 1000);
32 | });
33 | }
34 |
35 | util.inherits(Client, EventEmitter);
36 |
37 | exports.Client = Client;
38 |
--------------------------------------------------------------------------------
/web-server/static/init.js:
--------------------------------------------------------------------------------
1 | // var map;
2 | Activity.initialize = function() {
3 | var latlng = new google.maps.LatLng(30, 10);
4 | var myOptions = {
5 | zoom: 2,
6 | minZoom: 2,
7 | mapTypeControl: true,
8 | mapTypeControlOptions: {
9 | style: google.maps.MapTypeControlStyle.DROPDOWN_MENU
10 | },
11 | zoomControl: true,
12 | zoomControlOptions: {
13 | style: google.maps.ZoomControlStyle.SMALL
14 | },
15 |
16 | center: latlng,
17 | mapTypeId: google.maps.MapTypeId.ROADMAP
18 | };
19 | var map_canvas = document.getElementById("map_canvas");
20 | Activity.map = new google.maps.Map(map_canvas, myOptions);
21 |
22 | Activity.overlay = new Activity.Overlay(Activity.map);
23 | Activity.socket = io.connect('http://ctweereal/');
24 | };
25 |
26 | Activity.dispatcher = function(msg) {
27 | var message = msg.split(" ");
28 | if (message[0] === "0") {
29 | Activity.overlay.emitter.emit([message[1], message[2]], message[0]);
30 | } else if (message[0] === "1") {
31 | Activity.overlay.emitter.emit([message[1], message[2]], message[0], message[3]);
32 | }
33 | };
34 |
35 | window.onload = function() {
36 | Activity.initialize();
37 | new Activity.Interface();
38 | Activity.socket.on("message", Activity.dispatcher);
39 | };
--------------------------------------------------------------------------------
/configs/nginx/conf.d/default.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 8000;
3 | server_name localhost;
4 |
5 | #charset koi8-r;
6 | #access_log /var/log/nginx/log/host.access.log main;
7 |
8 | location / {
9 | root /usr/share/nginx/html;
10 | index index.html index.htm;
11 | }
12 |
13 | #error_page 404 /404.html;
14 |
15 | # redirect server error pages to the static page /50x.html
16 | #
17 | error_page 500 502 503 504 /50x.html;
18 | location = /50x.html {
19 | root /usr/share/nginx/html;
20 | }
21 |
22 | # proxy the PHP scripts to Apache listening on 127.0.0.1:80
23 | #
24 | #location ~ \.php$ {
25 | # proxy_pass http://127.0.0.1;
26 | #}
27 |
28 | # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
29 | #
30 | #location ~ \.php$ {
31 | # root html;
32 | # fastcgi_pass 127.0.0.1:9000;
33 | # fastcgi_index index.php;
34 | # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
35 | # include fastcgi_params;
36 | #}
37 |
38 | # deny access to .htaccess files, if Apache's document root
39 | # concurs with nginx's one
40 | #
41 | #location ~ /\.ht {
42 | # deny all;
43 | #}
44 | }
45 |
46 |
--------------------------------------------------------------------------------
/web-server/static/reset.css:
--------------------------------------------------------------------------------
1 | /* http://meyerweb.com/eric/tools/css/reset/
2 | v2.0 | 20110126
3 | License: none (public domain)
4 | */
5 |
6 | html, body, div, span, applet, object, iframe,
7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
8 | a, abbr, acronym, address, big, cite, code,
9 | del, dfn, em, img, ins, kbd, q, s, samp,
10 | small, strike, strong, sub, sup, tt, var,
11 | b, u, i, center,
12 | dl, dt, dd, ol, ul, li,
13 | fieldset, form, label, legend,
14 | table, caption, tbody, tfoot, thead, tr, th, td,
15 | article, aside, canvas, details, embed,
16 | figure, figcaption, footer, header, hgroup,
17 | menu, nav, output, ruby, section, summary,
18 | time, mark, audio, video {
19 | margin: 0;
20 | padding: 0;
21 | border: 0;
22 | font-size: 100%;
23 | font: inherit;
24 | vertical-align: baseline;
25 | }
26 | /* HTML5 display-role reset for older browsers */
27 | article, aside, details, figcaption, figure,
28 | footer, header, hgroup, menu, nav, section {
29 | display: block;
30 | }
31 | body {
32 | line-height: 1;
33 | }
34 | ol, ul {
35 | list-style: none;
36 | }
37 | blockquote, q {
38 | quotes: none;
39 | }
40 | blockquote:before, blockquote:after,
41 | q:before, q:after {
42 | content: '';
43 | content: none;
44 | }
45 | table {
46 | border-collapse: collapse;
47 | border-spacing: 0;
48 | }
--------------------------------------------------------------------------------
/configs/haproxy/haproxy.cfg:
--------------------------------------------------------------------------------
1 | global
2 | log 127.0.0.1 local0
3 | log 127.0.0.1 local1 notice
4 | maxconn 4096
5 | user haproxy
6 | group haproxy
7 | daemon
8 |
9 | defaults
10 | log global
11 | mode http
12 | option httplog
13 | option dontlognull
14 | retries 3
15 | option redispatch
16 | maxconn 2000
17 | contimeout 5000
18 | clitimeout 5000
19 | srvtimeout 5000
20 |
21 | frontend http-in
22 | bind *:80
23 | acl url_static_dir path_beg /static
24 | acl url_static_end path_end .jpg .jpeg .gif .png .ico .pdf .js .css .flv .swf
25 | acl is_www_clean hdr_sub(host) -i ctweereal
26 | # acl is_www_domain_com hdr_end(host) -i domain.com
27 |
28 | use_backend www_static if url_static_dir url_static_end
29 | use_backend www_clean if is_www_clean
30 | # use_backend www_domain_com if is_www_domain_com
31 | # default_backend www_example_com
32 |
33 | backend www_static
34 | option httpclose
35 | option forwardfor
36 | server StaticNginx 127.0.0.1:8000
37 |
38 | backend www_clean
39 | # balance roundrobin
40 | # cookie SERVERID insert nocache indirect
41 | # option httpchk HEAD /check.txt HTTP/1.0
42 | option httpclose
43 | option forwardfor
44 | server Server1 127.0.0.1:8001
45 | # server Server1 10.1.1.1:80 cookie Server1
46 | # server Server2 10.1.1.2:80 cookie Server2
47 |
--------------------------------------------------------------------------------
/lib/RestreamServer.js:
--------------------------------------------------------------------------------
1 | var Server = require('net').Server;
2 | var EventEmitter = require('events').EventEmitter;
3 | var util = require('util');
4 | var RestreamServer = function(port, host) {
5 | var self = this;
6 | var server = new Server();
7 |
8 | this.write = function(str) {
9 | self.emit("data", str);
10 | console.log(str);
11 | }
12 |
13 | server.listen(port, host);
14 |
15 | server.on("error",function(error) {
16 | console.log("ERROR at restreaming server:", error);
17 | setTimeout(function() {
18 | server.close();
19 | server.listen(port, host);
20 | }, 1000);
21 | });
22 |
23 | server.on("listening",function() {
24 | console.log("Server started at " + host + ":" + port);
25 | });
26 |
27 | server.on("connection", function(socket) {
28 | console.log("Socket init", (new Date()).toUTCString());
29 |
30 | socket.setKeepAlive(true);
31 | socket.setNoDelay(true);
32 | socket.setTimeout(1000);
33 |
34 | var write = function(data) {
35 | socket.write(data);
36 | };
37 |
38 | socket.on("timeout", function() {
39 | socket.end();
40 | });
41 |
42 | socket.on("end", function() {
43 | console.log("Socket ended", (new Date()).toUTCString());
44 | });
45 |
46 | socket.on("error", function(error) {
47 | console.log("Socket error:", error);
48 | });
49 |
50 | socket.on("close", function() {
51 | self.removeListener("data", write);
52 | });
53 |
54 | self.on("data", write);
55 | });
56 |
57 | server.on("close", function() {
58 | console.log("Server closed");
59 | });
60 | }
61 |
62 | util.inherits(RestreamServer, EventEmitter);
63 |
64 | exports.RestreamServer = RestreamServer;
--------------------------------------------------------------------------------
/web-server/express.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var app = express.createServer();
3 | var io = require('socket.io').listen(app);
4 | var redis = require("redis");
5 | var redisClient = redis.createClient();
6 | var EventEmitter = require('events').EventEmitter;
7 |
8 | var messageProxy = new EventEmitter();
9 | messageProxy.setMaxListeners(0);
10 |
11 | redisClient.subscribe("T");
12 | redisClient.on("message", function(channel, message) {
13 | if (channel === "T") {
14 | messageProxy.emit("message", message);
15 | }
16 | });
17 |
18 |
19 | app.listen(8001);
20 |
21 | io.configure(function() {
22 | io.enable('browser client minification'); // send minified client
23 | // io.enable('browser client etag'); // apply etag caching logic based on version number
24 | io.enable('browser client gzip'); // gzip the file
25 | io.set('log level', 1); // reduce logging
26 | io.set('transports', ['websocket', 'htmlfile', 'xhr-polling', 'jsonp-polling']);
27 | });
28 |
29 | // var client = new(require('./lib/Client.js').Client)(8090, "127.0.0.1");
30 | console.log("Started");
31 | app.configure(function() {
32 | app.use(express.errorHandler({
33 | showStack: true,
34 | dumpExceptions: true
35 | }));
36 | app.set('views', __dirname + '/views');
37 | app.disable('view cache');
38 | app.set('view options', {
39 | layout: false
40 | });
41 | });
42 |
43 | app.get('/', function(req, res) {
44 | res.render('index.ejs');
45 | });
46 |
47 | app.get('/pres', function(req, res) {
48 | res.render('pres.ejs');
49 | });
50 |
51 | io.sockets.on('connection', function(socket) {
52 | var handler = function(str) {
53 | socket.send(str);
54 | };
55 | messageProxy.on('message', handler);
56 |
57 | socket.on('disconnect', function() {
58 | messageProxy.removeListener("message", handler);
59 | });
60 |
61 | socket.on("reconnect_failed", function() {
62 | messageProxy.removeListener("message", handler);
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/web-server/static/basics.js:
--------------------------------------------------------------------------------
1 | var Activity = {
2 | "map": null
3 | };
4 |
5 | Activity.options = {
6 | "width": 960,
7 | "height": 540
8 | }
9 |
10 | Activity.utils = {
11 | extender: function(obj, properties) {
12 | for (var key in properties) obj[key] = properties[key];
13 | },
14 | removeAllChilds: function(div) {
15 | while (div.firstChild) {
16 | div.removeChild(div.firstChild);
17 | }
18 | },
19 | randomBgHex: function() {
20 | return "#" + (Math.floor(Math.random() * 255)).toString(16) + (Math.floor(Math.random() * 255)).toString(16) + (Math.floor(Math.random() * 255)).toString(16);
21 | },
22 | latLngToPx: function(latLng, projection) {
23 | var glt = new google.maps.LatLng(latLng[0], latLng[1]);
24 | var point = projection.fromLatLngToContainerPixel(glt);
25 | return [point.x, point.y];
26 | },
27 | filter: function(w, h, projection) {
28 | var lat = [];
29 | var lng = [];
30 | var points = [
31 | new google.maps.Point(0, 0), new google.maps.Point(w, 0), new google.maps.Point(w, h), new google.maps.Point(0, h)];
32 | var latLng = points.map(function(point) {
33 | var ll = projection.fromContainerPixelToLatLng(point);
34 | lat.push(ll.lat());
35 | lng.push(ll.lng());
36 | });
37 | return [[Math.min.apply(null, lat), Math.max.apply(null, lat)], [Math.min.apply(null, lng), Math.max.apply(null, lng)]];
38 | },
39 | randomColorArray: function() {
40 | return [Math.round(Math.random() * 255), Math.round(Math.random() * 255), Math.round(Math.random() * 255)];
41 | },
42 | tweetPhase: function(start, live) {
43 | return (Date.now() - start) / live;
44 | },
45 | makeRGBA: function(r, g, b, a) {
46 | // return "rgba(" + Array.prototype.join.call(arguments,",") + ")";
47 | return "rgba(" + r + "," + g + "," + b + "," + a + ")";
48 | },
49 | cachedRGB: function(c) {
50 | return "rgba(" + c[0] + "," + c[1] + "," + c[2] + ",";
51 | },
52 | addRGBAlpha: function(str, alpha) {
53 | return str + alpha + ")";
54 | }
55 | }
--------------------------------------------------------------------------------
/web-server/static/overlay.js:
--------------------------------------------------------------------------------
1 | Activity.Overlay = function(map) {
2 | this.map = map;
3 | this.div = null;
4 | this.setMap(map);
5 | this.divStyle = window.getComputedStyle(this.map.getDiv());
6 | };
7 |
8 | Activity.Overlay.prototype = new google.maps.OverlayView();
9 |
10 | Activity.Overlay.prototype.onAdd = function() {
11 | var overlay = this;
12 | var panes = overlay.getPanes();
13 | var overlayProjection = overlay.getProjection();
14 | var div = document.createElement('div');
15 | var overlayStyles = {
16 | width : Activity.width + "px",
17 | height : Activity.height + "px",
18 | position : "relative",
19 | overflow : "hidden"
20 | };
21 |
22 | overlay.div = div;
23 | div.id = "bubles";
24 | Activity.utils.extender(div.style, overlayStyles);
25 | panes.overlayLayer.appendChild(div);
26 |
27 | overlay.emitter = new Activity.CanvasEmitter(div);
28 | overlay.emitter.setProjection(overlayProjection);
29 | overlay.emitter.stop();
30 | overlay.emitter.start();
31 |
32 | google.maps.event.addListener(overlay.map, 'bounds_changed', function(event) {
33 | overlay.emitter.stop();
34 | });
35 |
36 | google.maps.event.addListener(overlay.map, 'idle', function(event) {
37 | overlay.emitter.updateSize(overlay.map.getZoom());
38 | overlay.draw();
39 | overlay.emitter.start();
40 | });
41 |
42 | };
43 |
44 | Activity.Overlay.prototype.draw = function() {
45 | var overlay = this;
46 | var overlayProjection = overlay.getProjection();
47 | var bounds = overlay.map.getBounds();
48 | var ne = bounds.getNorthEast();
49 | var sw = bounds.getSouthWest();
50 | var top = overlayProjection.fromLatLngToDivPixel(ne).y;
51 | var left = overlayProjection.fromLatLngToDivPixel(sw).x;
52 | var overlayStyles = {
53 | top : Math.round(top) + "px",
54 | left : Math.round(left) + "px"
55 | };
56 |
57 | Activity.utils.extender(overlay.div.style, overlayStyles);
58 | };
59 |
60 | Activity.Overlay.prototype.onRemove = function() {
61 | var overlay = this;
62 | overlay.div.parentNode.removeChild(overlay.div);
63 | overlay.div = null;
64 | };
--------------------------------------------------------------------------------
/api-client/restreamer.js:
--------------------------------------------------------------------------------
1 | var authConf = require('./apiConf.js').authConf;
2 | var reqConf = {
3 | port: 443,
4 | host: 'stream.twitter.com',
5 | https: true,
6 | path: '/1/statuses/filter.json',
7 | oauth_signature: "",
8 | method: 'POST',
9 | body: {
10 | locations: '-180,-90,180,90'
11 | }
12 | };
13 | var redis = require("redis");
14 | var streamer = new(require("./lib/Streamer.js").Streamer)(authConf, reqConf);
15 | var filters = {};
16 |
17 | var roundWithDepth = function(x, depthNum) {
18 | return Math.round(x * depthNum) / depthNum;
19 | };
20 |
21 | var boundingToCoordinates = function(bounding, /*maximal long*/ max) {
22 | var deltaLat = bounding[2][1] - bounding[0][1];
23 | var deltaLng = bounding[2][0] - bounding[0][0];
24 |
25 | if ((deltaLat > max) || (deltaLng > max)) return false;
26 |
27 | var lat = bounding[0][1] + deltaLat * Math.random();
28 | var lng = bounding[0][0] + deltaLng * Math.random();
29 | var precision = Math.max(deltaLat, deltaLng);
30 |
31 | var depthNum = 100000;
32 |
33 | return [
34 | roundWithDepth(lat, depthNum), roundWithDepth(lng, depthNum), roundWithDepth(precision, depthNum) //accuracy
35 | ];
36 | };
37 |
38 | var Quality = function() {
39 | var lastTrack = 0;
40 | var tweets = 0;
41 | this.getQuality = function(track) {
42 | var delta = track - lastTrack;
43 | if (delta < 0) {
44 | delta = track;
45 | }
46 | var quality = tweets / (tweets + delta);
47 | lastTrack = track;
48 | tweets = 0;
49 | return roundWithDepth(quality, 1000);
50 | };
51 | this.upTweets = function() {
52 | tweets++;
53 | };
54 | };
55 |
56 | var q = new Quality();
57 |
58 | var customFilter = function(tweet) {
59 | if (tweet.coordinates) {
60 | filters.exact(tweet);
61 | q.upTweets();
62 | } else if (tweet.place) {
63 | filters.notExact(tweet);
64 | q.upTweets();
65 | } else if (typeof tweet.limit !== "undefined") {
66 | filters.limit(tweet);
67 | } else {
68 | console.log("else", tweet);
69 | return false;
70 | }
71 | };
72 |
73 | var redisClient = redis.createClient();
74 |
75 | var write = function(message) {
76 | redisClient.publish("T", message);
77 | };
78 |
79 | filters.exact = function(tweet) {
80 | var coordinates = tweet.coordinates.coordinates.reverse();
81 | if (!(coordinates[0] === 0 && coordinates[1] === 0)) {
82 | write("0 " + coordinates[0] + " " + coordinates[1] + "\r\n");
83 | }
84 | };
85 |
86 | filters.notExact = function(tweet) {
87 | var boundCoordinates = boundingToCoordinates(tweet.place.bounding_box.coordinates[0], 2);
88 | if (boundCoordinates !== false) {
89 | write("1 " + boundCoordinates[0] + " " + boundCoordinates[1] + " " + boundCoordinates[2] + "\r\n");
90 | }
91 | };
92 |
93 | filters.limit = function(tweet) {
94 | var quality = q.getQuality(tweet.limit.track);
95 | write("2 " + quality + "\r\n");
96 | };
97 |
98 | streamer.stream();
99 | streamer.on("tweet", function(tweet) {
100 | customFilter(tweet);
101 | });
102 |
--------------------------------------------------------------------------------
/lib/Streamer.js:
--------------------------------------------------------------------------------
1 | var util = require('util'), oauth = require('oauth-client');
2 | var EventEmitter = require('events').EventEmitter;
3 | var parser = require("./parser.js").parser;
4 |
5 | //REMEMBER ABOUT CLOCK TIMESTAMP
6 |
7 | var Streamer = function(authConf, reqConf) {
8 | var self = this;
9 | var signer = oauth.createHmac(oauth.createConsumer(authConf.consumerKey, authConf.consumerSecret), oauth.createToken(authConf.tokenKey, authConf.tokenSecret));
10 | reqConf["oauth_signature"] = signer;
11 | var buffer = "";
12 |
13 | var socketTimeout = 1000;
14 | var networkTimeoutStart = 250;
15 | var networkTimeoutEnd = 16000;
16 | var httpTimeoutStart = 10000;
17 | var httpTimeoutEnd = 240000;
18 |
19 | var blockReconnection = false;
20 | var lastInterval = networkTimeoutStart;
21 |
22 | var reconnect = function(code) {
23 | if (blockReconnection) return false;
24 | blockReconnection = true;
25 | if (code) {
26 | if (lastInterval > httpTimeoutEnd) {
27 | lastInterval = httpTimeoutStart;
28 | } else {
29 | lastInterval *= 2;
30 | }
31 | } else {
32 | if (lastInterval > networkTimeoutEnd) {
33 | lastInterval = networkTimeoutStart;
34 | } else {
35 | lastInterval += networkTimeoutStart;
36 | }
37 | }
38 |
39 | setTimeout(function() {
40 | blockReconnection = false;
41 | self.stream()
42 |
43 | }, lastInterval);
44 |
45 | }
46 |
47 | var emitment = function(req, code, err) {
48 | return {
49 | "request":req,
50 | "wrongCode":code,
51 | "error":err
52 | }
53 | }
54 |
55 | var jsonHandler = function(json) {
56 | try {
57 | self.emit("tweet", JSON.parse(json));
58 | } catch (error) {
59 | console.log("Error in Streamer parser:", error);
60 | }
61 | }
62 |
63 | self.on("reconnect", function(opt) {
64 | console.log("Reconnect:", (new Date()).toUTCString());
65 | if (opt.error) {
66 | console.log("Error:", opt.error);
67 | }
68 | opt.request.abort();
69 | reconnect(opt.wrongCode);
70 | });
71 |
72 | self.stream = function() {
73 | var request = oauth.request(reqConf, function(response) {
74 | response.setEncoding('utf8');
75 | console.log("STATUS:", response.statusCode);
76 |
77 | if (response.statusCode !== 200) {
78 | self.emit("reconnect", emitment(request, true));
79 | }
80 |
81 | response.on("data", function(chunk) {
82 | // console.log(chunk);
83 | parser(chunk, buffer, jsonHandler);
84 | });
85 |
86 | response.on("end", function() {
87 | self.emit("reconnect", emitment(request, false));
88 | });
89 |
90 | response.on("close", function() {
91 | self.emit("reconnect", emitment(request, false));
92 | });
93 |
94 | response.on("error", function(err) {
95 | self.emit("reconnect", emitment(request, false, err));
96 | });
97 |
98 | });
99 |
100 | request.write(reqConf.body);
101 | request.end();
102 |
103 | request.on("error", function(err) {
104 | self.emit("reconnect", emitment(request, false, err));
105 | });
106 |
107 | request.on("socket", function(socket) {
108 | var socket = socket.socket;
109 | socket.setTimeout(socketTimeout);
110 | socket.setKeepAlive(true);
111 | socket.on("timeout", function() {
112 | self.emit("reconnect", emitment(request, false));
113 | });
114 | });
115 | }
116 | }
117 |
118 | util.inherits(Streamer, EventEmitter);
119 |
120 | exports.Streamer = Streamer;
--------------------------------------------------------------------------------
/web-server/static/interface.js:
--------------------------------------------------------------------------------
1 | Activity.Interface = function() {
2 | var options = Activity.Bubble.prototype.options;
3 |
4 | var sliders = [{
5 | "id": "all-sliders-size",
6 | "value": options.baseSize * 2,
7 | "handler": function(x) {
8 | options.baseSize = x;
9 | options.size = Math.round(Math.sqrt(options.zoom) * x);
10 | },
11 | "transform": function(y) {
12 | // return Math.round(Math.sqrt(options.zoom) * y * 0.5);
13 | return y * 0.5;
14 | }
15 | }, {
16 | "id": "all-sliders-live",
17 | "value": options.live / 100,
18 | "handler": function(x) {
19 | options.live = x;
20 | },
21 | "transform": function(y) {
22 | return Math.round(y * 100);
23 | }
24 | }, {
25 | "id": "exact-opacity",
26 | "value": 100,
27 | "handler": function(x) {
28 | options.neatFillOpacity = options.baseFillOpacity * x;
29 | options.neatStrokeOpacity = options.baseStrokeOpacity * x;
30 | },
31 | "transform": function(y) {
32 | return 0.01 * Math.round(y);
33 | }
34 | }, {
35 | "id": "places-opacity",
36 | "value": 50,
37 | "handler": function(x) {
38 | options.blurFillOpacity = options.baseFillOpacity * x;
39 | options.blurStrokeOpacity = options.baseStrokeOpacity * x;
40 | },
41 | "transform": function(y) {
42 | return 0.01 * Math.round(y);
43 | }
44 | }, {
45 | "id": "places-precision",
46 | "value": 50,
47 | "handler": function(x) {
48 | options.precision = x;
49 | },
50 | "transform": function(y) {
51 | return 0.02*Math.round(y);
52 | }
53 | }];
54 |
55 | for (var i = sliders.length - 1; i >= 0; i--) {
56 | sliders[i]["slider"] = new Activity.Interface.Slider(sliders[i]["id"], sliders[i]["value"], sliders[i]["handler"], sliders[i]["transform"]);
57 | };
58 |
59 | var switcherExact = new Activity.Interface.Switcher("exact-switcher", function() {
60 | Activity.Bubble.prototype.renderNeat = Activity.Bubble.prototype.renderNeatOn;
61 | }, function() {
62 | Activity.Bubble.prototype.renderNeat = function() {};
63 | });
64 |
65 | var switcherPlaces = new Activity.Interface.Switcher("places-switcher", function() {
66 | Activity.Bubble.prototype.renderBlur = Activity.Bubble.prototype.renderBlurOn;
67 | }, function() {
68 | Activity.Bubble.prototype.renderBlur = function() {};
69 | });
70 |
71 |
72 | $("#switch-defaults").on("click", function() {
73 | for (var i = sliders.length - 1; i >= 0; i--) {
74 | sliders[i]["slider"].toDefault();
75 | }
76 | switcherExact.on();
77 | switcherPlaces.on();
78 | });
79 |
80 | };
81 |
82 | Activity.Interface.Slider = function(id, value, handler, transform) {
83 | var self = this;
84 | this.defaults = value;
85 | this.handler = handler;
86 | this.transform = transform;
87 | this.slider = $("#" + id).slider({
88 | step: 0.01,
89 | animate: true,
90 | value: value,
91 | slide: function(event, ui) {
92 | self.handler(self.transform(ui.value));
93 | }
94 | });
95 | };
96 |
97 | Activity.Interface.Slider.prototype.toDefault = function() {
98 | this.slider.slider("value", this.defaults);
99 | this.handler(this.transform(this.defaults));
100 | console.log(this.transform(this.defaults));
101 | };
102 |
103 | Activity.Interface.Switcher = function(id, switchOn, switchOff) {
104 | var self = this;
105 | self.$el = $("#" + id);
106 | self.state = true;
107 | self.switchOn = switchOn;
108 | self.switchOff = switchOff;
109 |
110 | self.$el.on("click", function(event) {
111 | event.preventDefault();
112 | if (self.state) {
113 | self.off();
114 | } else {
115 | self.on();
116 | }
117 | return false;
118 | });
119 |
120 | };
121 |
122 | Activity.Interface.Switcher.prototype.on = function() {
123 | this.switchOn();
124 | this.$el.removeClass("off").addClass("on").text("on");
125 | this.state = true;
126 | }
127 |
128 | Activity.Interface.Switcher.prototype.off = function() {
129 | this.switchOff();
130 | this.$el.removeClass("on").addClass("off").text("off");
131 | this.state = false;
132 | }
133 |
--------------------------------------------------------------------------------
/web-server/static/emitter.js:
--------------------------------------------------------------------------------
1 | window.requestAnimFrame = (function() {
2 | return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
3 | function( /* function */ callback, /* DOMElement */ element) {
4 | window.setTimeout(callback, 1000 / 60);
5 | };
6 | })();
7 |
8 |
9 | Activity.Bubble = function(center, exact, precision) {
10 | var color = Activity.utils.randomColorArray();
11 | var self = this;
12 | this.precision = precision;
13 | // this.exact = exact;
14 | this.time = Date.now();
15 | this.cachedRGB = Activity.utils.cachedRGB(color);
16 | this.center = center;
17 | this.render = function(phase, ctx) {
18 | if (exact === "1") {
19 | if (precision <= self.options.precision) self.renderBlur(phase, ctx);
20 | } else {
21 | self.renderNeat(phase, ctx);
22 | }
23 | }
24 | };
25 |
26 |
27 | Activity.Bubble.prototype.renderNeatOn = function(phase, ctx) {
28 | ctx.beginPath();
29 | ctx.arc(this.center[0], this.center[1], this.options.size * phase, 0, 2 * Math.PI);
30 | ctx.fillStyle = Activity.utils.addRGBAlpha(this.cachedRGB, this.options.neatFillOpacity * (1 - phase));
31 | ctx.fill();
32 | ctx.strokeStyle = Activity.utils.addRGBAlpha(this.cachedRGB, this.options.neatStrokeOpacity * Math.sin((1 - phase) * Math.PI));
33 | ctx.stroke();
34 | ctx.closePath();
35 | };
36 |
37 | Activity.Bubble.prototype.renderNeat = Activity.Bubble.prototype.renderNeatOn;
38 |
39 | Activity.Bubble.prototype.renderBlurOn = function(phase, ctx) {
40 | ctx.beginPath();
41 | ctx.arc(this.center[0], this.center[1], this.options.size * phase, 0, 2 * Math.PI);
42 | ctx.fillStyle = Activity.utils.addRGBAlpha(this.cachedRGB, this.options.blurFillOpacity * (1 - phase));
43 | ctx.fill();
44 | ctx.strokeStyle = Activity.utils.addRGBAlpha(this.cachedRGB, this.options.blurStrokeOpacity * Math.sin((1 - phase) * Math.PI));
45 | ctx.stroke();
46 | ctx.closePath();
47 | };
48 |
49 | Activity.Bubble.prototype.renderBlur = Activity.Bubble.prototype.renderBlurOn;
50 |
51 | Activity.Bubble.prototype.options = {
52 | "size": 10,
53 | "baseSize": 10,
54 | "baseFillOpacity": 0.8,
55 | "baseStrokeOpacity": 1,
56 | "live": 750,
57 | "neatFillOpacity": 0.8,
58 | "neatStrokeOpacity": 1,
59 | "blurFillOpacity": 0.4,
60 | "blurStrokeOpacity": 0.5,
61 | "zoom": 2,
62 | "precision": 1
63 | };
64 |
65 | Activity.CanvasEmitter = function(div, options) {
66 | var self = this;
67 | var canvas = document.createElement("canvas");
68 | var bubbles = [];
69 | var allowed = true;
70 | var bubbleOptions = Activity.Bubble.prototype.options;
71 | var ctx = canvas.getContext("2d");
72 |
73 | var notInProjection = function(center) {
74 | return (center[0] < -bubbleOptions.size || center[0] > self.width + bubbleOptions.size || center[1] < -bubbleOptions.size || center[1] > self.height + bubbleOptions.size);
75 | };
76 |
77 | var tweetPhase = function(start, live) {
78 | return (Date.now() - start) / live;
79 | };
80 |
81 | var filterHandler = function(el) {
82 | var phase = tweetPhase(el.time, bubbleOptions.live);
83 | if (phase < 1) {
84 | el.render(phase, ctx);
85 | return true;
86 | } else {
87 | return false;
88 | }
89 | };
90 |
91 | var animation = function() {
92 | ctx.clearRect(0, 0, self.width, self.height);
93 |
94 | if (!allowed) {
95 | return false;
96 | }
97 |
98 | bubbles = bubbles.filter(filterHandler);
99 |
100 | window.requestAnimFrame(animation);
101 | };
102 |
103 | Activity.BasicEmitter.call(this, div, options);
104 |
105 | this.width = Activity.options.width;
106 | this.height = Activity.options.height;
107 |
108 | canvas.width = this.width;
109 | canvas.height = this.height;
110 |
111 | div.appendChild(canvas);
112 |
113 | this.emit = function(crd, exact, precision) {
114 | var center = Activity.utils.latLngToPx(crd, this.projection);
115 |
116 | if (!notInProjection(center)) {
117 | // console.log("o");
118 | bubbles.push(new Activity.Bubble(center, exact, precision));
119 | }
120 |
121 | };
122 |
123 | this.start = function() {
124 | allowed = true;
125 | window.requestAnimFrame(animation);
126 | };
127 |
128 | this.stop = function() {
129 | allowed = false;
130 | this.clear();
131 | };
132 |
133 | this.clear = function() {
134 | bubbles = [];
135 | ctx.clearRect(0, 0, self.width, self.height);
136 | };
137 |
138 | this.updateSize = function(zoom) {
139 | bubbleOptions.size = Math.round(Math.sqrt(zoom) * bubbleOptions.baseSize);
140 | // console.log(zoom);
141 | bubbleOptions.zoom = zoom;
142 | };
143 |
144 | };
145 |
--------------------------------------------------------------------------------
/web-server/static/style.css:
--------------------------------------------------------------------------------
1 | html {
2 | font-family: Arial, sans-serif;
3 | }
4 |
5 | body {
6 | background-image: url("images/project_papper.png");
7 | background-color: #eee;
8 | color: #515151;
9 | }
10 |
11 | .wrapper {
12 | min-width: 1000px;
13 | max-width: 1040px;
14 |
15 | margin: 0 auto;
16 | overflow: hidden;
17 | }
18 |
19 | .content {
20 | background-color: white;
21 | border-radius: 3px;
22 | outline: 10px solid rgba(255,255,255,0.4);
23 | width:960px;
24 | padding: 20px;
25 | overflow: hidden;
26 | position: relative;
27 | margin: 10px auto;
28 | margin-bottom: 20px;
29 | }
30 |
31 | .footer {
32 | width: 960px;
33 | padding: 20px;
34 | overflow: hidden;;
35 | }
36 |
37 | .footer a {
38 | display: block;
39 | float: left;
40 | opacity: 0.5;
41 | -webkit-transition: opacity ease-in 0.2s;
42 | -moz-transition: opacity ease-in 0.2s;
43 | -ms-transition: opacity ease-in 0.2s;
44 | -o-transition: opacity ease-in 0.2s;
45 | }
46 |
47 | .footer a:hover {
48 | opacity: 1;
49 | }
50 |
51 | .footer a:first-child {
52 | margin-bottom: 10px;
53 | margin-left: 15%;
54 | }
55 |
56 | .footer a:last-child {
57 | margin-left: 33%;
58 | }
59 |
60 | .head {
61 | width: 960px;
62 | padding: 20px;
63 | margin: 0 auto;
64 | }
65 |
66 | .linefirst a {
67 | text-decoration: none;
68 | }
69 |
70 | h1,h2,h3,h4 {
71 | font-family: Ubuntu, sans-serif;
72 | font-style: italic;
73 | font-size: 36px;
74 | color: rgba(227,50,88,1);
75 | }
76 |
77 | h2 {
78 | font-size: 30px;
79 | margin-bottom: 10px;
80 | }
81 |
82 | .sliders {
83 | margin-bottom: 10px;
84 |
85 | -moz-user-select:none;
86 | -webkit-user-select:none;
87 | user-select:none;
88 | -ms-user-select:none;
89 |
90 | clear: right;
91 | }
92 |
93 | .sliders-label, .slider-def, .switch-defaults {
94 | font-family: sans-serif;
95 | color: rgba(227,50,88,1);
96 | /*color: #515151;*/
97 | font-style: italic;
98 |
99 | }
100 |
101 | .sliders-label {
102 | font-size: 20px;
103 | margin-bottom: 7px;
104 | }
105 |
106 | .sliders-label > div {
107 | display: inline-block;
108 | }
109 |
110 | .slider-def {
111 | font-size: 14px;
112 | margin-bottom: 7px;
113 |
114 | }
115 |
116 | .linefirst h1 {
117 | float: left;
118 | text-shadow: 2px 2px 0 rgba(255,255,255,0.8);
119 | }
120 |
121 | .linefirst {
122 | overflow: hidden;
123 | width: 100%;
124 |
125 | }
126 |
127 | .social {
128 | float: right;
129 | margin-bottom: 6px;
130 | }
131 |
132 | #gplus {
133 | margin-bottom: 2px;
134 | }
135 |
136 | .like {
137 | float: left;
138 | }
139 |
140 | .like:last-child {
141 | margin: 0;
142 | }
143 |
144 | .contain {
145 | position: relative;
146 | overflow: hidden;
147 | width: 960px;
148 | height: 540px;
149 | padding-bottom: 10px;
150 | }
151 |
152 | #map_canvas {
153 | overflow: hidden;
154 | width: 100%;
155 | height: 100%;
156 | }
157 |
158 | #bubles {
159 | transform: translateZ(0);
160 | -webkit-transform: translateZ(0);
161 | -ms-transform: translateZ(0);
162 | -o-transform: translateZ(0);
163 | -moz-transform: translateZ(0);
164 | }
165 |
166 | .txt {
167 | width: 45%;
168 | line-height: 1.5;
169 | float: left;
170 | font-size: 14px;
171 | }
172 |
173 | .txt:last-child {
174 | margin-left:10%;
175 | }
176 |
177 | .link-twitter {
178 | color: rgba(0,180,255,1);
179 | text-decoration: none;
180 | }
181 |
182 | .switch-defaults {
183 | font-size: 12px;
184 | float: right;
185 | border-bottom: 1px dashed rgba(0, 180, 255, 1);
186 | padding-bottom: 1px;
187 | clear: left;
188 | cursor: pointer;
189 | color: rgba(0, 180, 255, 1);
190 | }
191 |
192 | .switch-on-off {
193 | color: white;
194 | background-color: rgba(0, 180, 255, 1);
195 | font-family: sans-serif;
196 | padding:1px 5px 1px 5px;
197 | line-height: 1.3;
198 | font-style: italic;
199 | font-size: 14px;
200 | border-radius: 3px;
201 | margin-left: 5px;
202 | /*font-weight: bold;*/
203 | cursor: pointer;
204 | }
205 |
206 | .on {
207 | box-shadow: 0 2px 0px rgba(0,180,255,1);
208 | background-color: rgba(0, 180, 255, 1);
209 | background: -moz-linear-gradient(bottom, rgba(84,206,255,1) 0%, rgba(0,180,255,1) 100%);
210 | background: -webkit-linear-gradient(bottom, rgba(84,206,255,1) 0%,rgba(0,180,255,1) 100%);
211 | background: -o-linear-gradient(bottom, rgba(84,206,255,1) 0%,rgba(0,180,255,1) 100%);
212 | background: -ms-linear-gradient(bottom, rgba(84,206,255,1) 0%,rgba(0,180,255,1) 100%);
213 | background: linear-gradient(bottom, rgba(84,206,255,1) 0%,rgba(0,180,255,1) 100%);
214 | }
215 |
216 | .off {
217 | box-shadow: 0 2px 0px rgba(227,50,88,1);
218 | background-color: rgba(227,50,88,1);
219 | background: -moz-linear-gradient(bottom, rgba(224,112,134,1) 0%, rgba(227,50,88,1) 100%);
220 | background: -webkit-linear-gradient(bottom, rgba(224,112,134,1) 0%,rgba(227,50,88,1) 100%);
221 | background: -o-linear-gradient(bottom, rgba(224,112,134,1) 0%,rgba(227,50,88,1) 100%);
222 | background: -ms-linear-gradient(bottom, rgba(224,112,134,1) 0%,rgba(227,50,88,1) 100%);
223 | background: linear-gradient(bottom, rgba(224,112,134,1) 0%,rgba(227,50,88,1) 100%);
224 |
225 | }
226 |
227 | .slider {
228 | width:100%;
229 | margin-bottom: 20px;
230 | }
231 |
232 | .slider-section {
233 | width: 30%;
234 | line-height: 1.5;
235 | float: left;
236 |
237 | }
238 |
239 | .slider-section:first-child {
240 | margin-right: 5%;
241 | }
242 |
243 | .slider-section:last-child {
244 | float: right;
245 | }
246 |
247 | .ui-widget-content {
248 | border: none;
249 |
250 | background: none !important;
251 | background-color: rgba(227,50,88,0.03) !important;
252 |
253 | box-shadow: inset 0 1px 1px rgba(200,200,200,1), inset 0 -1px 1px rgba(200,200,200,0.1) !important;
254 | cursor: pointer;
255 | }
256 |
257 | .ui-slider-handle {
258 | cursor: pointer !important;
259 | height: 16px !important;
260 | width: 22px !important;
261 | bottom: -5px !important;
262 | background: none !important;
263 | background-color: rgba(250,247,247,1) !important;
264 | background-image: url("images/lines.png") !important;
265 | background-repeat: no-repeat !important;
266 | background-position: center !important;
267 | border-color: rgba(200,200,200,0.2) !important;
268 | box-shadow: 0 1px 1px rgba(200,200,200,0.8);
269 | }
270 |
271 | .ui-state-active {
272 | outline:none;
273 | }
274 |
275 | .ui-state-focus {
276 | outline:none;
277 | }
278 |
279 | .ui-state-hover {
280 | }
281 |
282 | .ui-slider-horizontal {
283 | height: 8px;
284 | }
--------------------------------------------------------------------------------
/web-server/static/impress/README.md:
--------------------------------------------------------------------------------
1 | impress.js
2 | ============
3 |
4 | It's a presentation framework based on the power of CSS3 transforms and
5 | transitions in modern browsers and inspired by the idea behind prezi.com.
6 |
7 | **WARNING**
8 |
9 | impress.js may not help you if you have nothing interesting to say ;)
10 |
11 |
12 | ABOUT THE NAME
13 | ----------------
14 |
15 | impress.js name in [courtesy of @skuzniak](http://twitter.com/skuzniak/status/143627215165333504).
16 |
17 | It's an (un)fortunate coincidence that a Open/LibreOffice presentation tool is called Impress ;)
18 |
19 |
20 | VERSION HISTORY
21 | -----------------
22 |
23 | ### master (in development)
24 |
25 | **CONTAINS UNRELEASED CHANGES, MAY BE UNSTABLE**
26 |
27 | * minor CSS 3D fixes
28 | * basic API to control the presentation flow from JavaScript
29 | * touch event support
30 |
31 |
32 | ### 0.2 ([browse](http://github.com/bartaz/impress.js/tree/0.2), [zip](http://github.com/bartaz/impress.js/zipball/0.2), [tar](http://github.com/bartaz/impress.js/tarball/0.2))
33 |
34 | * tutorial/documentation added to `index.html` source file
35 | * being even more strict with strict mode
36 | * code clean-up
37 | * couple of small bug-fixes
38 |
39 |
40 | ### 0.1 ([browse](http://github.com/bartaz/impress.js/tree/0.1), [zip](http://github.com/bartaz/impress.js/zipball/0.1), [tar](http://github.com/bartaz/impress.js/tarball/0.1))
41 |
42 | First release.
43 |
44 | Contains basic functionality for step placement and transitions between them
45 | with simple fallback for non-supporting browsers.
46 |
47 |
48 |
49 | HOW TO USE IT
50 | ---------------
51 |
52 | [Use the source](http://github.com/bartaz/impress.js/blob/master/index.html), Luke ;)
53 |
54 | If you have no idea what I mean by that, or you just clicked that link above and got
55 | very confused by all these strange characters that got displayed on your screen,
56 | it's a sign, that impress.js is not for you.
57 |
58 | Sorry.
59 |
60 | Fortunately there are some guys on GitHub that got quite excited with the idea of building
61 | editing tool for impress.js. Let's hope they will manage to do it.
62 |
63 |
64 | EXAMPLES AND DEMOS
65 | --------------------
66 |
67 | ### Official demo
68 |
69 | [impress.js demo](http://bartaz.github.com/impress.js) by [@bartaz](http://twitter.com/bartaz)
70 |
71 | ### Presentations
72 |
73 | [CSS 3D transforms](http://bartaz.github.com/meetjs/css3d-summit) from [meet.js summit](http://summit.meetjs.pl) by [@bartaz](http://twitter.com/bartaz)
74 |
75 | [What the Heck is Responsive Web Design](http://johnpolacek.github.com/WhatTheHeckIsResponsiveWebDesign-impressjs/) by John Polacek [@johnpolacek](http://twitter.com/johnpolacek)
76 |
77 | [12412.org presentation to Digibury](http://extra.12412.org/digibury/) by Stephen Fulljames [@fulljames](http://twitter.com/fulljames)
78 |
79 | [Data center virtualization with Wakame-VDC](http://wakame.jp/wiki/materials/20120114_TLUG/) by Andreas Kieckens [@Metallion98](https://twitter.com/#!/Metallion98)
80 |
81 | [Asynchronous JavaScript](http://www.medikoo.com/asynchronous-javascript/3d/) by Mariusz Nowak [@medikoo](http://twitter.com/medikoo)
82 |
83 | [Introduction to Responsive Design](http://www.alecrust.com/factory/rd-presentation/) by Alec Rust [@alecrust] (http://twitter.com/alecrust)
84 |
85 | [Bonne année 2012](http://duael.fr/voeux/2012/) by Edouard Cunibil [@DuaelFr](http://twitter.com/DuaelFr)
86 |
87 | [Careers in Free and Open Source Software](http://exequiel09.github.com/symposium-presentation/) by Exequiel Ceasar Navarrete [@ichigo1411](http://twitter.com/ichigo1411)
88 |
89 | ### Websites and portfolios
90 |
91 | [lioshi.com](http://lioshi.com) by @lioshi
92 |
93 | [alingham.com](http://www.alingham.com) by Al Ingham [@alingham](http://twitter.com/alingham)
94 |
95 | [nice-shots.de](http://nice-shots.de) by [@NiceShots](http://twitter.com/NiceShots)
96 |
97 | [museum140](http://www.youtube.com/watch?v=ObLiikJEt94) Shorty Award promo video [entirely made with ImpressJS](http://thingsinjars.com/post/446/museum140-shorty/) by [@thingsinjars](http://twitter.com/thingsinjars)
98 |
99 |
100 | If you have used impress.js in your presentation (or website) and would like to have it listed here,
101 | please contact me via GitHub or send me a pull request to updated `README.md` file.
102 |
103 |
104 |
105 | BROWSER SUPPORT
106 | -----------------
107 |
108 | ### TL;DR;
109 |
110 | Currently impress.js works fine in latest Chrome/Chromium browser, Safari 5.1 and Firefox 10
111 | (to be released in January 2012). IE is currently not supported (IE10 is close, but not there
112 | yet - see below for details). It also doesn't work in Opera.
113 |
114 | As it was not developed with mobile browsers in mind, it currently doesn't work on
115 | any mobile devices, including tablets.
116 |
117 | ### Still interested? Read more...
118 |
119 | Additionally for the animations to run smoothly it's required to have hardware
120 | acceleration support in your browser. This depends on the browser, your operating
121 | system and even kind of graphic hardware you have in your machine.
122 |
123 | For browsers not supporting CSS3 3D transforms impress.js adds `impress-not-supported`
124 | class on `#impress` element, so fallback styles can be applied to make all the content accessible.
125 |
126 |
127 | ### Even more explanation and technical stuff
128 |
129 | Let's put this straight -- wide browser support was (and is) not on top of my priority list for
130 | impress.js. It's built on top of fresh technologies that just start to appear in the browsers
131 | and I'd like to rather look forward and develop for the future than being slowed down by the past.
132 |
133 | But it's not "hard-coded" for any particular browser or engine. If any browser in future will
134 | support features required to run impress.js, it will just begin to work there without changes in
135 | the code.
136 |
137 | From technical point of view all the positioning of presentation elements in 3D requires CSS 3D
138 | transforms support. Transitions between presentation steps are based on CSS transitions.
139 | So these two features are required by impress.js to display presentation correctly.
140 |
141 | Unfortunately the support for CSS 3D transforms and transitions is not enough for animations to
142 | run smoothly. If the browser doesn't support hardware acceleration or the graphic card is not
143 | good enough the transitions will be laggy.
144 |
145 | Additionally the code of impress.js relies on APIs proposed in HTML5 specification, including
146 | `classList` and `dataset` APIs. If they are not available in the browser, impress.js will not work.
147 |
148 | Fortunately, as these are JavaScript APIs there are polyfill libraries that patch older browsers
149 | with these APIs.
150 |
151 | For example IE10 is said to support CSS 3D transforms and transitions, but it doesn't have `classList`
152 | not `dataset` APIs implemented at the moment. So including polyfill libraries *should* help IE10
153 | with running impress.js.
154 |
155 |
156 | ### And few more details about mobile support
157 |
158 | Mobile browsers are currently not supported. Even iOS and Android browsers that support
159 | CSS 3D transforms are forced into fallback view at this point.
160 |
161 | Anyway, I'm really curious to see how modern mobile devices such as iPhone or iPad can
162 | handle such animations, so future mobile support is considered.
163 |
164 | iOS supports `classList` and `dataset` APIs starting with version 5, so iOS 4.X and older is not
165 | likely to be supported (without polyfill code).
166 |
167 |
168 | LICENSE
169 | ---------
170 |
171 | Copyright 2011-2012 Bartek Szopka. Released under MIT License.
172 |
173 |
--------------------------------------------------------------------------------
/web-server/views/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | sTweereal - real-time twitter activity map
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
41 |
42 |
45 |
Default settings
46 |
47 |
48 |
All tweets
49 |
Size
50 |
51 |
Live time
52 |
53 |
54 |
55 |
56 |
Exact tweets
57 |
on
58 |
59 |
Opacity
60 |
61 |
62 |
63 |
64 |
Places tweets
65 |
on
66 |
67 |
Opacity
68 |
69 |
Precision
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
About
78 |
79 | is the map of twitter users activity in real-time. Animation on the map include only tweets containing geo-tags. There are two types of tweets on a map: with the exact coordinates and the coordinates determined with an accuracy of 1 degree (more transparent). Based on Twitter Streaming API and Google Maps Javascript API. Developed by Artem Bey aka
80 |
81 |
82 |
83 |
Please feedback
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
95 |
96 |
97 |
98 |
106 |
107 |
138 |
139 |
--------------------------------------------------------------------------------
/web-server/static/impress/js/impress.js:
--------------------------------------------------------------------------------
1 | /**
2 | * impress.js
3 | *
4 | * impress.js is a presentation tool based on the power of CSS3 transforms and transitions
5 | * in modern browsers and inspired by the idea behind prezi.com.
6 | *
7 | * MIT Licensed.
8 | *
9 | * Copyright 2011 Bartek Szopka (@bartaz)
10 | */
11 |
12 | (function ( document, window ) {
13 | 'use strict';
14 |
15 | // HELPER FUNCTIONS
16 |
17 | var pfx = (function () {
18 |
19 | var style = document.createElement('dummy').style,
20 | prefixes = 'Webkit Moz O ms Khtml'.split(' '),
21 | memory = {};
22 |
23 | return function ( prop ) {
24 | if ( typeof memory[ prop ] === "undefined" ) {
25 |
26 | var ucProp = prop.charAt(0).toUpperCase() + prop.substr(1),
27 | props = (prop + ' ' + prefixes.join(ucProp + ' ') + ucProp).split(' ');
28 |
29 | memory[ prop ] = null;
30 | for ( var i in props ) {
31 | if ( style[ props[i] ] !== undefined ) {
32 | memory[ prop ] = props[i];
33 | break;
34 | }
35 | }
36 |
37 | }
38 |
39 | return memory[ prop ];
40 | }
41 |
42 | })();
43 |
44 | var arrayify = function ( a ) {
45 | return [].slice.call( a );
46 | };
47 |
48 | var css = function ( el, props ) {
49 | var key, pkey;
50 | for ( key in props ) {
51 | if ( props.hasOwnProperty(key) ) {
52 | pkey = pfx(key);
53 | if ( pkey != null ) {
54 | el.style[pkey] = props[key];
55 | }
56 | }
57 | }
58 | return el;
59 | }
60 |
61 | var byId = function ( id ) {
62 | return document.getElementById(id);
63 | }
64 |
65 | var $ = function ( selector, context ) {
66 | context = context || document;
67 | return context.querySelector(selector);
68 | };
69 |
70 | var $$ = function ( selector, context ) {
71 | context = context || document;
72 | return arrayify( context.querySelectorAll(selector) );
73 | };
74 |
75 | var translate = function ( t ) {
76 | return " translate3d(" + t.x + "px," + t.y + "px," + t.z + "px) ";
77 | };
78 |
79 | var rotate = function ( r, revert ) {
80 | var rX = " rotateX(" + r.x + "deg) ",
81 | rY = " rotateY(" + r.y + "deg) ",
82 | rZ = " rotateZ(" + r.z + "deg) ";
83 |
84 | return revert ? rZ+rY+rX : rX+rY+rZ;
85 | };
86 |
87 | var scale = function ( s ) {
88 | return " scale(" + s + ") ";
89 | };
90 |
91 | var getElementFromUrl = function () {
92 | // get id from url # by removing `#` or `#/` from the beginning,
93 | // so both "fallback" `#slide-id` and "enhanced" `#/slide-id` will work
94 | return byId( window.location.hash.replace(/^#\/?/,"") );
95 | };
96 |
97 | // CHECK SUPPORT
98 |
99 | var ua = navigator.userAgent.toLowerCase();
100 | var impressSupported = ( pfx("perspective") != null ) &&
101 | ( document.body.classList ) &&
102 | ( document.body.dataset ) &&
103 | ( ua.search(/(iphone)|(ipod)|(android)/) == -1 );
104 |
105 | var roots = {};
106 |
107 | var impress = window.impress = function ( rootId ) {
108 |
109 | rootId = rootId || "impress";
110 |
111 | // if already initialized just return the API
112 | if (roots["impress-root-" + rootId]) {
113 | return roots["impress-root-" + rootId];
114 | }
115 |
116 | // DOM ELEMENTS
117 |
118 | var root = byId( rootId );
119 |
120 | if (!impressSupported) {
121 | root.className = "impress-not-supported";
122 | return;
123 | } else {
124 | root.className = "";
125 | }
126 |
127 | // viewport updates for iPad
128 | var meta = $("meta[name='viewport']") || document.createElement("meta");
129 | // hardcoding these values looks pretty bad, as they kind of depend on the content
130 | // so they should be at least configurable
131 | meta.content = "width=1024, minimum-scale=0.75, maximum-scale=0.75, user-scalable=no";
132 | if (meta.parentNode != document.head) {
133 | meta.name = 'viewport';
134 | document.head.appendChild(meta);
135 | }
136 |
137 | var canvas = document.createElement("div");
138 | canvas.className = "canvas";
139 |
140 | arrayify( root.childNodes ).forEach(function ( el ) {
141 | canvas.appendChild( el );
142 | });
143 | root.appendChild(canvas);
144 |
145 | var steps = $$(".step", root);
146 |
147 | // SETUP
148 | // set initial values and defaults
149 |
150 | document.documentElement.style.height = "100%";
151 |
152 | css(document.body, {
153 | height: "100%",
154 | overflow: "hidden"
155 | });
156 |
157 | var props = {
158 | position: "absolute",
159 | transformOrigin: "top left",
160 | transition: "all 0s ease-in-out",
161 | transformStyle: "preserve-3d"
162 | }
163 |
164 | css(root, props);
165 | css(root, {
166 | top: "50%",
167 | left: "50%",
168 | perspective: "1000px"
169 | });
170 | css(canvas, props);
171 |
172 | var current = {
173 | translate: { x: 0, y: 0, z: 0 },
174 | rotate: { x: 0, y: 0, z: 0 },
175 | scale: 1
176 | };
177 |
178 | var stepData = {};
179 |
180 | var isStep = function ( el ) {
181 | return !!(el && el.id && stepData["impress-" + el.id]);
182 | }
183 |
184 | steps.forEach(function ( el, idx ) {
185 | var data = el.dataset,
186 | step = {
187 | translate: {
188 | x: data.x || 0,
189 | y: data.y || 0,
190 | z: data.z || 0
191 | },
192 | rotate: {
193 | x: data.rotateX || 0,
194 | y: data.rotateY || 0,
195 | z: data.rotateZ || data.rotate || 0
196 | },
197 | scale: data.scale || 1,
198 | el: el
199 | };
200 |
201 | if ( !el.id ) {
202 | el.id = "step-" + (idx + 1);
203 | }
204 |
205 | stepData["impress-" + el.id] = step;
206 |
207 | css(el, {
208 | position: "absolute",
209 | transform: "translate(-50%,-50%)" +
210 | translate(step.translate) +
211 | rotate(step.rotate) +
212 | scale(step.scale),
213 | transformStyle: "preserve-3d"
214 | });
215 |
216 | });
217 |
218 | // making given step active
219 |
220 | var active = null;
221 | var hashTimeout = null;
222 |
223 | var goto = function ( el ) {
224 | if ( !isStep(el) || el == active) {
225 | // selected element is not defined as step or is already active
226 | return false;
227 | }
228 |
229 | // Sometimes it's possible to trigger focus on first link with some keyboard action.
230 | // Browser in such a case tries to scroll the page to make this element visible
231 | // (even that body overflow is set to hidden) and it breaks our careful positioning.
232 | //
233 | // So, as a lousy (and lazy) workaround we will make the page scroll back to the top
234 | // whenever slide is selected
235 | //
236 | // If you are reading this and know any better way to handle it, I'll be glad to hear about it!
237 | window.scrollTo(0, 0);
238 |
239 | var step = stepData["impress-" + el.id];
240 |
241 | if ( active ) {
242 | active.classList.remove("active");
243 | }
244 | el.classList.add("active");
245 |
246 | root.className = "step-" + el.id;
247 |
248 | // `#/step-id` is used instead of `#step-id` to prevent default browser
249 | // scrolling to element in hash
250 | //
251 | // and it has to be set after animation finishes, because in chrome it
252 | // causes transtion being laggy
253 | window.clearTimeout( hashTimeout );
254 | hashTimeout = window.setTimeout(function () {
255 | window.location.hash = "#/" + el.id;
256 | }, 1000);
257 |
258 | var target = {
259 | rotate: {
260 | x: -parseInt(step.rotate.x, 10),
261 | y: -parseInt(step.rotate.y, 10),
262 | z: -parseInt(step.rotate.z, 10)
263 | },
264 | translate: {
265 | x: -step.translate.x,
266 | y: -step.translate.y,
267 | z: -step.translate.z
268 | },
269 | scale: 1 / parseFloat(step.scale)
270 | };
271 |
272 | // check if the transition is zooming in or not
273 | var zoomin = target.scale >= current.scale;
274 |
275 | // if presentation starts (nothing is active yet)
276 | // don't animate (set duration to 0)
277 | var duration = (active) ? "1s" : "0";
278 |
279 | css(root, {
280 | // to keep the perspective look similar for different scales
281 | // we need to 'scale' the perspective, too
282 | perspective: step.scale * 1000 + "px",
283 | transform: scale(target.scale),
284 | transitionDuration: duration,
285 | transitionDelay: (zoomin ? "500ms" : "0ms")
286 | });
287 |
288 | css(canvas, {
289 | transform: rotate(target.rotate, true) + translate(target.translate),
290 | transitionDuration: duration,
291 | transitionDelay: (zoomin ? "0ms" : "500ms")
292 | });
293 |
294 | current = target;
295 | active = el;
296 |
297 | return el;
298 | };
299 |
300 | var prev = function () {
301 | var prev = steps.indexOf( active ) - 1;
302 | prev = prev >= 0 ? steps[ prev ] : steps[ steps.length-1 ];
303 |
304 | return goto(prev);
305 | };
306 |
307 | var next = function () {
308 | var next = steps.indexOf( active ) + 1;
309 | next = next < steps.length ? steps[ next ] : steps[ 0 ];
310 |
311 | return goto(next);
312 | };
313 |
314 | window.addEventListener("hashchange", function () {
315 | goto( getElementFromUrl() );
316 | }, false);
317 |
318 | window.addEventListener("orientationchange", function () {
319 | window.scrollTo(0, 0);
320 | }, false);
321 |
322 | // START
323 | // by selecting step defined in url or first step of the presentation
324 | goto(getElementFromUrl() || steps[0]);
325 |
326 | return (roots[ "impress-root-" + rootId ] = {
327 | goto: goto,
328 | next: next,
329 | prev: prev
330 | });
331 |
332 | }
333 | })(document, window);
334 |
335 | // EVENTS
336 |
337 | (function ( document, window ) {
338 | 'use strict';
339 |
340 | // keyboard navigation handler
341 | document.addEventListener("keydown", function ( event ) {
342 | if ( event.keyCode == 9 || ( event.keyCode >= 32 && event.keyCode <= 34 ) || (event.keyCode >= 37 && event.keyCode <= 40) ) {
343 | switch( event.keyCode ) {
344 | case 33: ; // pg up
345 | case 37: ; // left
346 | case 38: // up
347 | impress().prev();
348 | break;
349 | case 9: ; // tab
350 | case 32: ; // space
351 | case 34: ; // pg down
352 | case 39: ; // right
353 | case 40: // down
354 | impress().next();
355 | break;
356 | }
357 |
358 | event.preventDefault();
359 | }
360 | }, false);
361 |
362 | // delegated handler for clicking on the links to presentation steps
363 | document.addEventListener("click", function ( event ) {
364 | // event delegation with "bubbling"
365 | // check if event target (or any of its parents is a link)
366 | var target = event.target;
367 | while ( (target.tagName != "A") &&
368 | (target != document.body) ) {
369 | target = target.parentNode;
370 | }
371 |
372 | if ( target.tagName == "A" ) {
373 | var href = target.getAttribute("href");
374 |
375 | // if it's a link to presentation step, target this step
376 | if ( href && href[0] == '#' ) {
377 | target = document.getElementById( href.slice(1) );
378 | }
379 | }
380 |
381 | if ( impress().goto(target) ) {
382 | event.stopImmediatePropagation();
383 | event.preventDefault();
384 | }
385 | }, false);
386 |
387 | // delegated handler for clicking on step elements
388 | document.addEventListener("click", function ( event ) {
389 | var target = event.target;
390 | // find closest step element
391 | while ( !target.classList.contains("step") &&
392 | (target != document.body) ) {
393 | target = target.parentNode;
394 | }
395 |
396 | if ( impress().goto(target) ) {
397 | event.preventDefault();
398 | }
399 | }, false);
400 |
401 | // touch handler to detect taps on the left and right side of the screen
402 | document.addEventListener("touchstart", function ( event ) {
403 | if (event.touches.length === 1) {
404 | var x = event.touches[0].clientX,
405 | width = window.innerWidth * 0.3,
406 | result = null;
407 |
408 | if ( x < width ) {
409 | result = impress().prev();
410 | } else if ( x > window.innerWidth - width ) {
411 | result = impress().next();
412 | }
413 |
414 | if (result) {
415 | event.preventDefault();
416 | }
417 | }
418 | }, false);
419 | })(document, window);
420 |
421 |
--------------------------------------------------------------------------------
/web-server/static/impress/css/impress-demo.css:
--------------------------------------------------------------------------------
1 | /**
2 | * This is a stylesheet for a demo presentation for impress.js
3 | *
4 | * It is not meant to be a part of impress.js and is not required by impress.js.
5 | * I expect that anyone creating a presentation for impress.js would create their own
6 | * set of styles.
7 | */
8 |
9 |
10 | /* http://meyerweb.com/eric/tools/css/reset/
11 | v2.0 | 20110126
12 | License: none (public domain)
13 | */
14 |
15 | html, body, div, span, applet, object, iframe,
16 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
17 | a, abbr, acronym, address, big, cite, code,
18 | del, dfn, em, img, ins, kbd, q, s, samp,
19 | small, strike, strong, sub, sup, tt, var,
20 | b, u, i, center,
21 | dl, dt, dd, ol, ul, li,
22 | fieldset, form, label, legend,
23 | table, caption, tbody, tfoot, thead, tr, th, td,
24 | article, aside, canvas, details, embed,
25 | figure, figcaption, footer, header, hgroup,
26 | menu, nav, output, ruby, section, summary,
27 | time, mark, audio, video {
28 | margin: 0;
29 | padding: 0;
30 | border: 0;
31 | font-size: 100%;
32 | font: inherit;
33 | vertical-align: baseline;
34 | }
35 |
36 | /* HTML5 display-role reset for older browsers */
37 | article, aside, details, figcaption, figure,
38 | footer, header, hgroup, menu, nav, section {
39 | display: block;
40 | }
41 | body {
42 | line-height: 1;
43 | }
44 | ol, ul {
45 | list-style: none;
46 | }
47 | blockquote, q {
48 | quotes: none;
49 | }
50 | blockquote:before, blockquote:after,
51 | q:before, q:after {
52 | content: '';
53 | content: none;
54 | }
55 |
56 | table {
57 | border-collapse: collapse;
58 | border-spacing: 0;
59 | }
60 |
61 |
62 | body {
63 | font-family: 'PT Sans', sans-serif;
64 |
65 | min-height: 740px;
66 |
67 | /*background: rgb(215, 215, 215);*/
68 | background: white;
69 | /*background: -webkit-gradient(radial, 50% 50%, 0, 50% 50%, 500, from(rgb(240, 240, 240)), to(rgb(190, 190, 190)));*/
70 | /*background: -webkit-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190));*/
71 | /*background: -moz-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190));*/
72 | /*background: -o-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190));*/
73 | /*background: radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190));*/
74 | background-image: url(http://tweereal.com/static/images/project_papper.png);
75 |
76 | -webkit-font-smoothing: antialiased;
77 | }
78 |
79 | b, strong { font-weight: bold }
80 | i, em { font-style: italic}
81 |
82 | a {
83 | color: inherit;
84 | text-decoration: none;
85 | padding: 0 0.1em;
86 | background: rgba(255,255,255,0.5);
87 | text-shadow: -1px -1px 2px rgba(100,100,100,0.9);
88 | border-radius: 0.2em;
89 |
90 | -webkit-transition: 0.5s;
91 | -moz-transition: 0.5s;
92 | -ms-transition: 0.5s;
93 | -o-transition: 0.5s;
94 | transition: 0.5s;
95 | }
96 |
97 | a:hover {
98 | background: rgba(255,255,255,1);
99 | text-shadow: -1px -1px 2px rgba(100,100,100,0.5);
100 | }
101 |
102 | /* enable clicking on elements 'hiding' behind body in 3D */
103 | body { pointer-events: none; }
104 | #impress { pointer-events: auto; }
105 |
106 | /* COMMON STEP STYLES */
107 |
108 | .step {
109 | width: 900px;
110 | padding: 40px;
111 |
112 | -webkit-box-sizing: border-box;
113 | -moz-box-sizing: border-box;
114 | -ms-box-sizing: border-box;
115 | -o-box-sizing: border-box;
116 | box-sizing: border-box;
117 |
118 | font-family: 'PT Serif', georgia, serif;
119 |
120 | font-size: 48px;
121 | line-height: 1.5;
122 | }
123 |
124 | /* fade out inactive slides */
125 |
126 | .step {
127 | -webkit-transition: opacity 1s;
128 | -moz-transition: opacity 1s;
129 | -ms-transition: opacity 1s;
130 | -o-transition: opacity 1s;
131 | transition: opacity 1s;
132 | }
133 |
134 | .step:not(.active) {
135 | opacity: 0.3;
136 | }
137 |
138 | /* STEP SPECIFIC STYLES */
139 |
140 | /* hint on the first slide */
141 |
142 | .hint {
143 | position: fixed;
144 | left: 0;
145 | right: 0;
146 | bottom: 200px;
147 |
148 | background: rgba(0,0,0,0.5);
149 | color: #EEE;
150 | text-align: center;
151 |
152 | font-size: 50px;
153 | padding: 20px;
154 |
155 | z-index: 100;
156 |
157 | opacity: 0;
158 |
159 | -webkit-transform: translateY(400px);
160 | -moz-transform: translateY(400px);
161 | -ms-transform: translateY(400px);
162 | -o-transform: translateY(400px);
163 | transform: translateY(400px);
164 |
165 | -webkit-transition: opacity 1s, -webkit-transform 0.5s 1s;
166 | -moz-transition: opacity 1s, -moz-transform 0.5s 1s;
167 | -ms-transition: opacity 1s, -ms-transform 0.5s 1s;
168 | -o-transition: opacity 1s, -o-transform 0.5s 1s;
169 | transition: opacity 1s, transform 0.5s 1s;
170 | }
171 |
172 | .step-bored + .hint {
173 | opacity: 1;
174 |
175 | -webkit-transition: opacity 1s 5s, -webkit-transform 0.5s;
176 | -moz-transition: opacity 1s 5s, -moz-transform 0.5s;
177 | -ms-transition: opacity 1s 5s, -ms-transform 0.5s;
178 | -o-transition: opacity 1s 5s, -o-transform 0.5s;
179 | transition: opacity 1s 5s, transform 0.5s;
180 |
181 | -webkit-transform: translateY(0px);
182 | -moz-transform: translateY(0px);
183 | -ms-transform: translateY(0px);
184 | -o-transform: translateY(0px);
185 | transform: translateY(0px);
186 | }
187 |
188 | /* impress.js title */
189 |
190 | #title {
191 | padding: 0;
192 | }
193 |
194 | #title .try {
195 | font-size: 64px;
196 | position: absolute;
197 | top: -0.5em;
198 | left: 1.5em;
199 |
200 | -webkit-transform: translateZ(20px);
201 | -moz-transform: translateZ(20px);
202 | -ms-transform: translateZ(20px);
203 | -o-transform: translateZ(20px);
204 | transform: translateZ(20px);
205 | }
206 |
207 | #title h1 {
208 | font-size: 190px;
209 |
210 | -webkit-transform: translateZ(50px);
211 | -moz-transform: translateZ(50px);
212 | -ms-transform: translateZ(50px);
213 | -o-transform: translateZ(50px);
214 | transform: translateZ(50px);
215 | }
216 |
217 | #title .footnote {
218 | font-size: 32px;
219 | }
220 |
221 | /* big thoughts */
222 |
223 | #big {
224 | width: 600px;
225 | text-align: center;
226 | font-size: 60px;
227 | line-height: 1;
228 | }
229 |
230 | #big b {
231 | display: block;
232 | font-size: 250px;
233 | line-height: 250px;
234 | }
235 |
236 | #big .thoughts {
237 | font-size: 90px;
238 | line-height: 150px;
239 | }
240 |
241 | /* tiny ideas */
242 |
243 | #tiny {
244 | width: 500px;
245 | text-align: center;
246 | }
247 |
248 | #ing {
249 | width: 500px;
250 | }
251 |
252 | #ing b {
253 | display: inline-block;
254 | -webkit-transition: 0.5s;
255 | -moz-transition: 0.5s;
256 | -ms-transition: 0.5s;
257 | -o-transition: 0.5s;
258 | transition: 0.5s;
259 | }
260 |
261 | #ing.active .positioning {
262 | -webkit-transform: translateY(-10px);
263 | -moz-transform: translateY(-10px);
264 | -ms-transform: translateY(-10px);
265 | -o-transform: translateY(-10px);
266 | transform: translateY(-10px);
267 |
268 | -webkit-transition-delay: 1.5s;
269 | -moz-transition-delay: 1.5s;
270 | -ms-transition-delay: 1.5s;
271 | -o-transition-delay: 1.5s;
272 | transition-delay: 1.5s;
273 | }
274 |
275 | #ing.active .rotating {
276 | -webkit-transform: rotate(-10deg);
277 | -moz-transform: rotate(-10deg);
278 | -ms-transform: rotate(-10deg);
279 | -o-transform: rotate(-10deg);
280 | transform: rotate(-10deg);
281 |
282 | -webkit-transition-delay: 1.75s;
283 | -moz-transition-delay: 1.75s;
284 | -ms-transition-delay: 1.75s;
285 | -o-transition-delay: 1.75s;
286 | transition-delay: 1.75s;
287 | }
288 |
289 | #ing.active .scaling {
290 | -webkit-transform: scale(0.7);
291 | -moz-transform: scale(0.7);
292 | -ms-transform: scale(0.7);
293 | -o-transform: scale(0.7);
294 | transform: scale(0.7);
295 |
296 | -webkit-transition-delay: 2s;
297 | -moz-transition-delay: 2s;
298 | -ms-transition-delay: 2s;
299 | -o-transition-delay: 2s;
300 | transition-delay: 2s;
301 |
302 | }
303 |
304 | /* imagination */
305 |
306 | #imagination {
307 | width: 600px;
308 | }
309 |
310 | #imagination .imagination {
311 | font-size: 78px;
312 | }
313 |
314 | /* use the source, Luke */
315 |
316 | #source {
317 | width: 700px;
318 | padding-bottom: 300px;
319 |
320 | /* Yoda Icon :: Pixel Art from Star Wars http://www.pixeljoint.com/pixelart/1423.htm */
321 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARgAAAEYCAMAAACwUBm+AAAAAXNSR0IArs4c6QAAAKtQTFRFsAAAvbWSLUUrLEQqY1s8UYJMqJ1vNTEgOiIdIzYhjIFVLhsXZ6lgSEIsP2U8JhcCVzMsSXZEgXdOO145XJdWOl03LzAYMk4vSXNExr+hwcuxRTs1Qmk+RW9Am49eFRANQz4pUoNMQWc+OSMDTz0wLBsCNVMxa2NBOyUDUoNNSnlEWo9VRGxAVzYFl6tXCggHbLNmMUIcHhwTXkk5f3VNRT8wUT8xAAAACQocRBWFFwAAAAF0Uk5TAEDm2GYAAAPCSURBVHja7d3JctNAFIZRMwRCCGEmzPM8z/D+T8bu/ptbXXJFdij5fMt2Wuo+2UgqxVmtttq5WVotLzBgwIABAwYMGDCn0qVqbo69psPqVpWx+1XG5iaavF8wYMCAAQMGDBgwi4DJ6Y6qkxB1HNlcN3a92gbR5P2CAQMGDBgwYMCAWSxMlrU+UY5yu2l9okfV4bAxUVbf7TJnAwMGDBgwYMCAAbMLMHeqbGR82Zy+VR1Ht81nVca6R+UdTLaU24Ruzd3qM/e4yjnAgAEDBgwYMGDA7AJMd1l/3NRdVGcj3eX/2WEhCmDGxnM7yqygu8XIPjJj8iN/MGDAgAEDBgwYMAuDGb8q0RGlLCHLv1t9qDKWn3vdNHVuEI6HPaxO9Jo3GDBgwIABAwYMmIXBdC9ShGgMk+XnkXUeuGcsP/e1+lhNnZsL/G5Vs3OAAQMGDBgwYMCAWSxMR3SzOmraG5atdy9wZKzb+vg16qyqe2FltbnAgAEDBgwYMGDALAxmTJSuN3WA76rnVca6GTnemGN1WoEBAwYMGDBgwIBZGMxUomy4+xO899V4LAg5Xnc2MGDAgAEDBgwYMGA218Wq+2K1LDqvY9xZu8zN8fICdM6btYABAwYMGDBgwIABMzfH0+pGU5afze2tXebmeAfVz+p8BQYMGDBgwIABAwbMPBzZ+oWmfJrln1273FhkbHzee9WWbw7AgAEDBgwYMGDALAKm43hcdctKgblcPamOhuXnXlY5Xs6bsW4FGyQCAwYMGDBgwIABswiYMceZKgvMo+h8mrHLTdn676rj+FEFoTtHd8MwOxEYMGDAgAEDBgyYRcBM5UhXqiymW3R3c9ARhWO/OmjqfjVZy+xEYMCAAQMGDBgwYBYG073OnCV0RFNhMhaOa9WfKmOB6XjHMN1tQmaAAQMGDBgwYMCA2VWY7vXjz1U4croAzgPztwIDBgwYMGDAgAEDZhswh035NBw59Dww3RgYMGDAgAEDBgwYMJuD6f4tXT7NUqfCdBvZLkxXdgQGDBgwYMCAAQNmt2DGj8WzwAfV/w7T/aq7mxwwYMCAAQMGDBgwuwqTOo7uTwTngflSzQ3TdaJvAwEDBgwYMGDAgAED5gSvgbyo5oHZ4Pc+gwEDBgwYMGDAgAEzhOm+5G0qTGaAAQMGDBgwYMCAAXNaMOcnls3tNwWm+zRzp54NDBgwYMCAAQMGDJh5YNL36k1TLuGvVq+qnKMbS5n7tulT9asCAwYMGDBgwIABA2ZumKuztLnjgQEDBgwYMGDAgNl5mH/4/ltKA6vBNAAAAABJRU5ErkJggg==);
322 | background-position: bottom right;
323 | background-repeat: no-repeat;
324 | }
325 |
326 | #source q {
327 | font-size: 60px;
328 | }
329 |
330 | /* it's in 3D */
331 |
332 | #its-in-3d p {
333 | -webkit-transform-style: preserve-3d;
334 | -moz-transform-style: preserve-3d; /* Y U need this Firefox?! */
335 | -ms-transform-style: preserve-3d;
336 | -o-transform-style: preserve-3d;
337 | transform-style: preserve-3d;
338 | }
339 |
340 | #its-in-3d span,
341 | #its-in-3d b {
342 | display: inline-block;
343 | -webkit-transform: translateZ(40px);
344 | -moz-transform: translateZ(40px);
345 | -ms-transform: translateZ(40px);
346 | -o-transform: translateZ(40px);
347 | transform: translateZ(40px);
348 |
349 | -webkit-transition: 0.5s;
350 | -moz-transition: 0.5s;
351 | -ms-transition: 0.5s;
352 | -o-transition: 0.5s;
353 | transition: 0.5s;
354 | }
355 |
356 | #its-in-3d .have {
357 | -webkit-transform: translateZ(-40px);
358 | -moz-transform: translateZ(-40px);
359 | -ms-transform: translateZ(-40px);
360 | -o-transform: translateZ(-40px);
361 | transform: translateZ(-40px);
362 | }
363 |
364 | #its-in-3d .you {
365 | -webkit-transform: translateZ(20px);
366 | -moz-transform: translateZ(20px);
367 | -ms-transform: translateZ(20px);
368 | -o-transform: translateZ(20px);
369 | transform: translateZ(20px);
370 | }
371 |
372 | #its-in-3d .noticed {
373 | -webkit-transform: translateZ(-40px);
374 | -moz-transform: translateZ(-40px);
375 | -ms-transform: translateZ(-40px);
376 | -o-transform: translateZ(-40px);
377 | transform: translateZ(-40px);
378 | }
379 |
380 | #its-in-3d .its {
381 | -webkit-transform: translateZ(60px);
382 | -moz-transform: translateZ(60px);
383 | -ms-transform: translateZ(60px);
384 | -o-transform: translateZ(60px);
385 | transform: translateZ(60px);
386 | }
387 |
388 | #its-in-3d .in {
389 | -webkit-transform: translateZ(-10px);
390 | -moz-transform: translateZ(-10px);
391 | -ms-transform: translateZ(-10px);
392 | -o-transform: translateZ(-10px);
393 | transform: translateZ(-10px);
394 | }
395 |
396 | #its-in-3d .footnote {
397 | font-size: 32px;
398 |
399 | -webkit-transform: translateZ(-10px);
400 | -moz-transform: translateZ(-10px);
401 | -ms-transform: translateZ(-10px);
402 | -o-transform: translateZ(-10px);
403 | transform: translateZ(-10px);
404 | }
405 |
406 | #its-in-3d.active span,
407 | #its-in-3d.active b {
408 | -webkit-transform: translateZ(0px);
409 | -moz-transform: translateZ(0px);
410 | -ms-transform: translateZ(0px);
411 | -o-transform: translateZ(0px);
412 | transform: translateZ(0px);
413 |
414 | -webkit-transition-delay: 1s;
415 | -moz-transition-delay: 1s;
416 | -ms-transition-delay: 1s;
417 | -o-transition-delay: 1s;
418 | transition-delay: 1s;
419 | }
420 |
421 | /* overview step */
422 |
423 | #overview {
424 | z-index: -1;
425 | padding: 0;
426 | }
427 |
428 | /* on overview step everything is visible */
429 |
430 | #impress.step-overview .step {
431 | opacity: 1;
432 | cursor: pointer;
433 | }
434 |
435 | /*
436 | * SLIDE STEP STYLES
437 | *
438 | * inspired by: http://html5slides.googlecode.com/svn/trunk/styles.css
439 | *
440 | * ;)
441 | */
442 |
443 | .lstul {
444 | /*margin: 0 auto;*/
445 | }
446 |
447 | .slide {
448 | display: block;
449 |
450 | width: 900px;
451 | height: 700px;
452 |
453 | padding: 40px 60px;
454 |
455 | border-radius: 10px;
456 |
457 | background-color: white;
458 |
459 | box-shadow: 0 2px 6px rgba(0, 0, 0, .1);
460 | border: 1px solid rgba(0, 0, 0, .3);
461 |
462 | font-family: 'Open Sans', Arial, sans-serif;
463 |
464 | /*color: rgb(102, 102, 102);*/
465 | color: rgb(227, 50, 88);
466 | text-shadow: 0 2px 2px rgba(0, 0, 0, .1);
467 |
468 | font-size: 30px;
469 | line-height: 36px;
470 |
471 | letter-spacing: -1px;
472 | }
473 |
474 | .slide q {
475 | display: block;
476 | font-size: 50px;
477 | line-height: 72px;
478 |
479 | margin-top: 100px;
480 | }
481 |
482 | .slide q strong {
483 | white-space: nowrap;
484 | }
485 |
486 |
487 | /* IMPRESS NOT SUPPORTED STYLES */
488 |
489 | .fallback-message {
490 | font-family: sans-serif;
491 | line-height: 1.3;
492 |
493 | display: none;
494 | width: 780px;
495 | padding: 10px 10px 0;
496 | margin: 20px auto;
497 |
498 | border-radius: 10px;
499 | border: 1px solid #E4C652;
500 | background: #EEDC94;
501 | }
502 |
503 | .fallback-message p {
504 | margin-bottom: 10px;
505 | }
506 |
507 | .impress-not-supported .step {
508 | position: relative;
509 | opacity: 1;
510 | margin: 20px auto;
511 | }
512 |
513 | .impress-not-supported .fallback-message {
514 | display: block;
515 | }
516 |
517 |
--------------------------------------------------------------------------------
/web-server/static/impress/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
46 |
47 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | impress.js | presentation tool based on the power of CSS3 transforms and transitions in modern browsers | by Bartek Szopka @bartaz
72 |
73 |
74 |
75 |
76 |
77 |
78 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
114 |
115 |
116 |
117 |
Your browser doesn't support the features required by impress.js, so you are presented with a simplified version of this presentation.
118 |
For the best experience please use the latest Chrome or Safari browser. Firefox 10 (to be released soon) will also handle it.
119 |
120 |
121 |
138 |
139 | Aren't you just bored with all those slides-based presentations?
140 |
141 |
142 |
157 |
158 | Don't you think that presentations given in modern browsers shouldn't copy the limits of 'classic' slide decks?
159 |
160 |
161 |
162 | Would you like to impress your audience with stunning visualization of your talk?
163 |
164 |
165 |
175 |
176 | then you should try
177 |
impress.js*
178 |
179 |
180 |
181 |
189 |
190 |
It's a presentation tool
191 | inspired by the idea behind prezi.com
192 | and based on the power of CSS3 transforms and transitions in modern browsers.
193 |
194 |
195 |
196 |
visualize your big thoughts
197 |
198 |
199 |
208 |
209 |
and tiny ideas
210 |
211 |
212 |
213 |
by positioning , rotating and scaling them on an infinite canvas
214 |
215 |
216 |
217 |
the only limit is your imagination
218 |
219 |
220 |
224 |
225 |
226 |
one more thing...
227 |
228 |
229 |
241 |
242 |
have you noticed it's in 3D* ?
243 |
244 |
245 |
246 |
258 |
259 |
260 |
261 |
262 |
263 |
277 |
278 |
Use a spacebar or arrow keys to navigate
279 |
280 |
285 |
286 |
301 |
302 |
303 |
304 |
325 |
326 |
327 |
328 |
329 |
357 |
358 |
373 |
374 |
--------------------------------------------------------------------------------
/web-server/views/pres.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
46 |
47 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | Tweereal
72 |
73 |
74 |
75 |
76 |
77 |
78 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
114 |
115 |
116 |
117 |
Your browser doesn't support the features required by impress.js, so you are presented with a simplified version of this presentation.
118 |
For the best experience please use the latest Chrome or Safari browser. Firefox 10 (to be released soon) will also handle it.
119 |
120 |
121 |
138 |
139 |
Node.js Real-time Application
140 | Tweereal.com – example of real-time node.js application
141 |
142 |
143 |
158 |
159 |
163 |
Framework
164 |
165 |
166 | Node.js
167 | Socket.io
168 | Express
169 |
170 |
171 |
172 |
173 |
174 |
Architecture
175 | Streaming API → Filtering → Socket.io → HAProxy
176 | Client → Express → HAProxy
177 | Client → nginx
178 |
179 |
180 |
181 |
Stability
182 | init.d
183 | forever
184 |
185 |
186 |
187 |
188 |
Senq for your attention
189 | Artem Bey: defly.self@gmail.com
190 | http://tweereal.com
191 | @tweereal
192 | @defly_self
193 |
194 |
195 |
196 |
206 |
211 |
212 |
220 |
229 |
230 |
239 |
260 |
272 |
277 |
289 |
290 |
291 |
292 |
293 |
294 |
308 |
309 |
Use a spacebar or arrow keys to navigate
310 |
311 |
316 |
317 |
332 |
333 |
334 |
335 |
356 |
357 |
358 |
359 |
360 |
388 |
389 |
404 |
405 |
--------------------------------------------------------------------------------