]> git.zarvox.org Git - imoo.git/commitdiff
End-to-end connectivity demo
authorDrew Fisher <drew.m.fisher@gmail.com>
Wed, 7 May 2014 21:15:03 +0000 (14:15 -0700)
committerDrew Fisher <drew.m.fisher@gmail.com>
Wed, 7 May 2014 21:15:03 +0000 (14:15 -0700)
Enough tornado and react.js to get events passing from browser to chat network
and back.

reactornado/dispatcher.js [new file with mode: 0644]
reactornado/index.html [new file with mode: 0644]
reactornado/main.js [new file with mode: 0644]
reactornado/ribbon_passthrough.py [new file with mode: 0644]
reactornado/style.css [new file with mode: 0644]
reactornado/widgets.js [new file with mode: 0644]

diff --git a/reactornado/dispatcher.js b/reactornado/dispatcher.js
new file mode 100644 (file)
index 0000000..872ae41
--- /dev/null
@@ -0,0 +1,13 @@
+// I have no idea if this is good form or not
+var Dispatcher = {
+    send_msg: function(destination, method, params) {
+        var m = {
+            "destination": destination,
+            "method": method,
+            "params": params
+        };
+        window.ws_conn.send(JSON.stringify(m));
+    }
+};
+
+window.Dispatcher = Dispatcher;
diff --git a/reactornado/index.html b/reactornado/index.html
new file mode 100644 (file)
index 0000000..3fa7df3
--- /dev/null
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+       <head>
+               <link rel="stylesheet" href="style.css">
+               <!-- The core React library, plus some experimental addons -->
+               <script src="http://fb.me/react-with-addons-0.10.0.js"></script>
+               <!-- In-browser JSX transformer, remove when pre-compiling JSX. -->
+               <script src="http://fb.me/JSXTransformer-0.10.0.js"></script>
+               <script type="text/jsx" src="widgets.js"></script>
+               <script type="text/javascript" src="dispatcher.js"></script>
+               <script type="text/javascript" src="main.js"></script>
+       </head>
+       <body>
+               <!-- A div for the initial login form -->
+               <div id="loginform"></div>
+               <!-- A div for the buddy list -->
+               <div id="blist"></div>
+               <!-- A div for the tabbed chat view -->
+               <div id="chats"></div>
+       </body>
+</html>
diff --git a/reactornado/main.js b/reactornado/main.js
new file mode 100644 (file)
index 0000000..76cf334
--- /dev/null
@@ -0,0 +1,33 @@
+// this is like a main.js
+
+window.blist = {}
+
+window.onload = function () {
+    // Create websocket and attach to window
+    var ws = new WebSocket("ws://localhost:8889/ws");
+
+    ws.onmessage = function(evt) {
+        //console.log("message received: " + evt.data);
+        var message = JSON.parse(evt.data)
+        console.log(message);
+    };
+    ws.onclose = function(evt) {
+        console.log("connection closed");
+    };
+    ws.onopen = function(evt) {
+        console.log("opened");
+    };
+    window.ws_conn = ws;
+
+    // Attach React-rendered components
+    React.renderComponent(
+        window.widgets.LoginForm(),
+        document.getElementById('loginform')
+    );
+
+    window.blist = React.renderComponent(
+        window.widgets.BuddyList(),
+        document.getElementById('blist')
+    );
+    console.log("loaded");
+};
diff --git a/reactornado/ribbon_passthrough.py b/reactornado/ribbon_passthrough.py
new file mode 100644 (file)
index 0000000..8d3c110
--- /dev/null
@@ -0,0 +1,88 @@
+import json
+import logging
+import os
+import socket
+import struct
+import subprocess
+
+import tornado.httpserver
+import tornado.ioloop
+import tornado.web
+import tornado.websocket
+
+pwd = os.path.dirname(os.path.abspath(__file__))
+
+logging.basicConfig(
+        level=logging.DEBUG,
+        format='%(asctime)s : %(levelname)s : %(name)s : %(message)s',
+        )
+
+log = logging.getLogger()
+
+class WSHandler(tornado.websocket.WebSocketHandler):
+    def on_ribbon_length_received(self, data):
+        length = struct.unpack(">i", data)[0]
+        log.info("ribbon sent length {}".format(length))
+        self.ribbon_stream.read_bytes(length, self.on_ribbon_message)
+
+    def on_ribbon_message(self, message):
+        log.info("ribbon sent data {}".format(message))
+        # Dispatch it straight to the frontend; #nofilter
+        self.write_message(message)
+        # And prepare for the next message
+        self.ribbon_stream.read_bytes(4, self.on_ribbon_length_received)
+
+    def on_ribbon_ready(self):
+        log.info("ribbon ready")
+        self.ribbon_ready = True
+        self.dispatch_backlog()
+        # Set up reads to trigger the length callback
+        self.ribbon_stream.read_bytes(4, self.on_ribbon_length_received)
+
+    def dispatch_backlog(self):
+        log.info("dispatch backlog")
+        for message in self.backlog:
+            log.info("write {}".format(message))
+            length = struct.pack(">I", len(message))
+            self.ribbon_stream.write(length)
+            self.ribbon_stream.write(message)
+        self.backlog = []
+
+    def queue_message(self, message):
+        self.backlog.append(message)
+        if self.ribbon_ready:
+            self.dispatch_backlog()
+
+    def open(self):
+        # Create a new connection to ribbon.
+        self.ribbon_ready = False
+        self.backlog = [] # A list of bytestrings queued to be sent on the socket.
+        self.ribbon_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
+        self.ribbon_stream = tornado.iostream.IOStream(self.ribbon_socket)
+        self.ribbon_stream.connect( ("localhost", 8888), self.on_ribbon_ready)
+        log.info('new connection')
+        self.write_message('{"key":"value"}')
+    def on_message(self, message):
+        log.info('message received: %r', message)
+        data = json.loads(message)
+        self.queue_message(message.encode("latin1"))
+
+    def on_close(self):
+        log.info('connection closed')
+
+
+application = tornado.web.Application([
+    (r'/ws', WSHandler),
+])
+
+
+if __name__ == "__main__":
+    # Spawn the ribbon subprocess
+    ribbon_binary = os.path.join(os.path.dirname(pwd), "ribbon", "ribbon")
+    ribbon = subprocess.Popen( [ribbon_binary], close_fds=True )
+
+    # Spawn the http server
+    http_server = tornado.httpserver.HTTPServer(application)
+    http_server.listen(8889)
+    log.info("starting up")
+    tornado.ioloop.IOLoop.instance().start()
diff --git a/reactornado/style.css b/reactornado/style.css
new file mode 100644 (file)
index 0000000..8268956
--- /dev/null
@@ -0,0 +1,46 @@
+.form {
+       float: left;
+}
+
+.form > label {
+       display: block;
+}
+.form > label > input {
+       display: block;
+       margin-bottom: 20px;
+}
+
+#blist {
+       float: right;
+}
+
+/* buddy list things */
+.blistitem {
+       width: 200px;
+       height: 48px;
+       font-size: small;
+}
+
+.blistitem > img {
+       display: inline-block;
+       width: 48px;
+       height: 48px;
+       float: left;
+}
+
+.blistitemcontainer {
+       display: inline-block;
+       vertical-align: top;
+       width: 152px;
+}
+
+.blist-buddyname {
+       font-weight: bold;
+}
+
+.blisttext {
+       text-overflow: ellipsis;
+       overflow: hidden;
+       white-space: nowrap;
+       width: 100%;
+}
diff --git a/reactornado/widgets.js b/reactornado/widgets.js
new file mode 100644 (file)
index 0000000..be04c3d
--- /dev/null
@@ -0,0 +1,99 @@
+/** @jsx React.DOM */
+
+// Testing stuff
+var List = React.createClass({
+    render: function () {
+        var items = this.props.items;
+        var listitems = [];
+        for (var i = 0 ; i < items.length ; i++) {
+            listitems.push(<ListItem key={i}>{items[i]}</ListItem>);
+        }
+        return <ul>{listitems}</ul>;
+    }
+});
+
+var ListItem = React.createClass({
+    render: function () {
+        return <li>{this.props.children}</li>;
+    }
+});
+
+
+// Login form
+
+var LoginForm = React.createClass({
+    mixins: [React.addons.LinkedStateMixin],
+    getInitialState: function () {
+        return {
+            "protocol": "prpl-jabber",
+            "username": "",
+            "password": ""
+        };
+    },
+    handleLogin: function () {
+        console.log("Imma log in now");
+        command = {
+                  "destination": "accounts",
+                  "method": "create_account",
+                  "params": this.state
+        };
+        window.ws_conn.send(JSON.stringify(command));
+    },
+
+    render: function () {
+        return (
+        <form className="form">
+        <label>Chat Network:<select name="chatnetwork" valueLink={this.linkState('protocol')}>
+            <option value="prpl-jabber">Jabber</option>
+            <option value="prpl-aim">AIM</option>
+        </select></label>
+        <label>Username:<input type="text" id="username" valueLink={this.linkState('username')} /></label>
+        <label>Password:<input type="password" id="password" valueLink={this.linkState('password')} /></label>
+        <input type="button" id="login" value="Login" onClick={this.handleLogin} />
+        </form>
+        );
+    }
+});
+
+
+// Buddy list stuff
+
+var BuddyListItem = React.createClass({
+    render: function () {
+        return <div className="blistitem">
+        <img className="buddyicon" src={this.props.bicon_url} />
+        <div className="blistitemcontainer">
+            <div className="blist-buddyname blisttext">{this.props.display_name}</div>
+            <div className="blisttext">{this.props.status}</div>
+        </div>
+    </div>;
+    }
+});
+
+var BuddyList = React.createClass({
+    getInitialState: function () {
+        return {"buddies": []};
+    },
+    onChange: function(newData) {
+        this.setState( { "buddies": newData } );
+    },
+    render: function () {
+        // for each thing in this.state:
+        //   render thing
+        var things = [];
+        for (var i = 0 ; i < this.state.buddies.length ; i++) {
+            var s = this.state.buddies[i];
+            things.push(BuddyListItem(this.state.buddies[i]));
+        }
+        return <div className="blist">{ things }</div>;
+    }
+});
+
+// integration for now:
+window.widgets = {
+    "List": List,
+    "ListItem": ListItem,
+    "BuddyListItem": BuddyListItem,
+    "LoginForm": LoginForm,
+    "BuddyList": BuddyList
+};